commit 782bea26af2b4a8753b9658cc51cd3d46104183e Author: 李鹏宇 Date: Fri Jun 26 09:40:27 2026 +0800 1 diff --git a/yuthon_notice/__init__.py b/yuthon_notice/__init__.py new file mode 100644 index 0000000..724cd13 --- /dev/null +++ b/yuthon_notice/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import models +from . import controllers \ No newline at end of file diff --git a/yuthon_notice/__manifest__.py b/yuthon_notice/__manifest__.py new file mode 100644 index 0000000..fd3f9e7 --- /dev/null +++ b/yuthon_notice/__manifest__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Yuthon notice", + 'summary': "通知公告", + 'description': """ + + """, + 'author': 'pengyuthon', + 'website': 'https://www.pengyuthon.com', + 'category': 'Human Resources/Attendances', + 'version': '0.1', + 'license': 'LGPL-3', + 'depends': ['base', 'hr', 'mail'], + 'data': [ + 'security/ir.model.access.csv', + 'data/notice_code_data.xml', + 'data/notice_cron_data.xml', + 'data/notice_type_data.xml', + 'views/yuthon_notice_views.xml', + 'views/yuthon_notice_type_views.xml', + 'views/yuthon_cs_tree_views.xml', + 'views/yuthon_confirm_users_views.xml', + ], + 'assets': { + 'web.assets_backend': [ + 'yuthon_notice/static/src/js/custom_create_button.js', + 'yuthon_notice/static/src/xml/custom_create_button.xml', + 'yuthon_notice/static/scss/notice_scss.scss', + ], + }, + 'installable': True, + 'application': True, +} \ No newline at end of file diff --git a/yuthon_notice/controllers/__init__.py b/yuthon_notice/controllers/__init__.py new file mode 100644 index 0000000..527c0a0 --- /dev/null +++ b/yuthon_notice/controllers/__init__.py @@ -0,0 +1 @@ +from . import main \ No newline at end of file diff --git a/yuthon_notice/controllers/main.py b/yuthon_notice/controllers/main.py new file mode 100644 index 0000000..cd0af36 --- /dev/null +++ b/yuthon_notice/controllers/main.py @@ -0,0 +1,133 @@ +import base64 +import json + +from odoo import http, fields +from odoo.http import request +from urllib.parse import quote + + +class YuthonNotice(http.Controller): + + @http.route('/yuthon/yuthon/notice', methods=['POST'], type='http', auth='none', csrf=False) + def index(self, **kwargs): + id = kwargs.get('id') + + data = request.env['yuthon.notice'].sudo().search([("id", "=", int(id))]) + documents = [] + for document in data.documents_ids: + documents.append({ + 'id': document.id, + 'name': document.name, + 'url': document.url, + 'type': document.type, + 'file_size': document.file_size, + 'file_size_display':document.file_size_display, + 'mimetype': document.mimetype, + 'datas': document.datas.decode('utf-8') if document.datas else None, + }) + + result = { + 'id': data.id, + 'title': data.name, + 'code': data.code, + 'create_date1': fields.Date.to_string(data.create_date1), + 'notice_text': data.notice_text, + 'documents':documents, + 'user_name':data.users_id.name, + 'read_number': data.read_number, + } + return json.dumps({ + 'data':result, + 'code':200, + 'message':'success' + }) + + @http.route('/yuthon/notice/list', methods=['POST'], type='http', auth='none', csrf=False) + def yuthon_notice_list(self, **kwargs): + user_id = kwargs.get('users_id') # 按创建人筛选 + domain = [] + if user_id: + user_id_int = int(user_id) + domain.append(('users_id', '=', user_id_int)) + notice_data = request.env['yuthon.notice'].sudo().search(domain) + result_list = [] + urgency_map = {'normal': '普件', 'urgent': '急件', 'emergency': '特急件'} + for notice in notice_data: + department_list = [] + file_list = [] + for attach in notice.documents_ids: + file_url = 'http://view.officeapps.live.com/op/view.aspx?src=' + quote('https://oa.thtzjt.com' + '/web/content/office_preview/' + str(attach.id)) + file_list.append({'file_url': file_url, 'file_name': attach.display_name}) + result = { + 'id': notice.id, + 'title': notice.name, + 'code': notice.code, + 'notice_text': notice.notice_text, + 'create_date1': fields.Date.to_string(notice.create_date1), + 'user_name': notice.users_id.name, + 'read_number': notice.read_number or 0, + 'department_ids': department_list, + 'file_list': file_list, + 'urgency_level': notice.urgency_level, + 'urgency_name': urgency_map.get(notice.urgency_level, ''), + } + result_list.append(result) + return json.dumps({ + 'data': result_list, + 'code': 200, + 'message': 'success' + }) + + + @http.route('/yuthon/notice/record', methods=['POST'], type='http', auth='none', csrf=False) + def yuthon_notice_record(self, **kwargs): + id = kwargs.get('id') + + data = request.env['yuthon.notice'].sudo().search([("id", "=", int(id))]) + + department_list = [] + for dept in data.department_ids: + department_list.append({ + 'id': dept.id, + 'name': dept.name, + }) + + role_group_list = [ + {'id': group.id, 'name': group.name} # res.groups的name字段是角色组名称 + for group in data.users_role_group_ids + ] + + file_list = [] + for attach in data.documents_ids: + attach_id = request.env['ir.attachment'].sudo().search([('id', '=', attach.id)]) + file_url = 'http://view.officeapps.live.com/op/view.aspx?src=' + quote('https://oa.thtzjt.com' + '/web/content/office_preview/' + str(attach_id.id)) + file_list.append({ + 'file_url': file_url, + 'file_name': attach_id.display_name, + }) + + urgency_map = {'normal': '普件','urgent': '急件','emergency': '特急件',} + + urgency_name = urgency_map.get(data.urgency_level) + + result = { + 'id': data.id, + 'title': data.name, + 'code': data.code, + 'notice_text': data.notice_text, + 'create_date1': fields.Date.to_string(data.create_date1), + 'user_name': data.users_id.name, + 'read_number': data.read_number, + 'department_ids': department_list, + 'users_role_group_ids': role_group_list, + 'file_list': file_list, + 'urgency_name': urgency_name, + } + return json.dumps({ + 'data':result, + 'code':200, + 'message':'success' + }) + + + diff --git a/yuthon_notice/data/notice_code_data.xml b/yuthon_notice/data/notice_code_data.xml new file mode 100644 index 0000000..69a0950 --- /dev/null +++ b/yuthon_notice/data/notice_code_data.xml @@ -0,0 +1,24 @@ + + + + 通知公告编号 + notice_code + TZ%(y)s%(month)s%(day)s + 3 + + + + + diff --git a/yuthon_notice/data/notice_cron_data.xml b/yuthon_notice/data/notice_cron_data.xml new file mode 100644 index 0000000..c9c3b55 --- /dev/null +++ b/yuthon_notice/data/notice_cron_data.xml @@ -0,0 +1,15 @@ + + + + + 公告未确认通知 + + code + model.sync_confirm_users_notices() + + + 1 + days + + + \ No newline at end of file diff --git a/yuthon_notice/data/notice_type_data.xml b/yuthon_notice/data/notice_type_data.xml new file mode 100644 index 0000000..ce6d7e6 --- /dev/null +++ b/yuthon_notice/data/notice_type_data.xml @@ -0,0 +1,7 @@ + + + 通知 + 公告 + 决定 + 其他 + \ No newline at end of file diff --git a/yuthon_notice/models/__init__.py b/yuthon_notice/models/__init__.py new file mode 100644 index 0000000..b3a43c5 --- /dev/null +++ b/yuthon_notice/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import yuthon_notice +from . import yuthon_notice_type +from . import yuthon_confirm_users + diff --git a/yuthon_notice/models/yuthon_confirm_users.py b/yuthon_notice/models/yuthon_confirm_users.py new file mode 100644 index 0000000..a1e013c --- /dev/null +++ b/yuthon_notice/models/yuthon_confirm_users.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, tools +from odoo.exceptions import UserError, ValidationError + + +class YuthonConfirmUsers(models.Model): + _name = "yuthon.confirm.users" + _description = "公告确认信息" + + employee_id = fields.Many2one('hr.employee', string='员工') + notice_id = fields.Many2one('yuthon.notice', string="公告名称") + notice_code = fields.Char(string="公告编号") + notice_name = fields.Char(string="公告名称") + state = fields.Selection([('no', '未确认'), ('yes', '已确认')], string="状态") + + def sync_confirm_users_notices(self): + menu_id = self.env['ir.ui.menu'].search([('name', '=', '通知公告')]) + for record in self.search([('state', '=', 'no')]): + partner_ids = record.employee_id.user_id.partner_id.id + record.with_context(lang=record.env.lang)._message_auto_subscribe_notify(partner_ids, 'yuthon_notice.message_yuthon_confirm_users_data') + url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.notice&view_type=list&menu_id={menu_id.id}' + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=record.employee_id.wecom_userid, title="通知公告", + description="您有待确认的通知公告请尽快确认", url=url, btntxt="详细信息") + + def reminders_notice(self): + for rem in self: + if rem.state == 'yes': + raise UserError('已确认不可催办') + confirm_noice_ids = self.env["yuthon.confirm.users"].browse(self._context.get('active_ids', self._context.get('active_id'))) + for rec in confirm_noice_ids: + menu_id = self.env['ir.ui.menu'].search([('name', '=', '通知公告')]) + url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.notice&view_type=list&menu_id={menu_id.id}' + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=rec.employee_id.wecom_userid, + title="通知公告(催办)", description="您有待确认的通知公告请尽快确认", + url=url, btntxt="详细信息") + partner_ids = rec.employee_id.user_id.partner_id.id + rec.with_context(lang=rec.env.lang)._message_auto_subscribe_notify(partner_ids, + 'yuthon_notice.message_yuthon_confirm_users_data') diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py new file mode 100644 index 0000000..46cfa01 --- /dev/null +++ b/yuthon_notice/models/yuthon_notice.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, tools, _ +from odoo.exceptions import ValidationError, UserError +import logging + +_logger = logging.getLogger(__name__) + + +class YuthonNotice(models.Model): + _name = "yuthon.notice" + _description = '通知公告' + _inherit = ['mail.thread.main.attachment', 'mail.activity.mixin'] + _rec_name = 'name' + _order = "sequence desc" + + name = fields.Char(string='标题') + code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('notice_code')) + users_id = fields.Many2one('res.users', string="发布人", default=lambda self: self.env.user) + company_id = fields.Many2one(related="users_id.employee_id.company_id", string="公司", store=True) + department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门") + notice_type_id = fields.Many2one('yuthon.notice.type', string="类型") + create_date1 = fields.Date(string="发布日期", default=fields.Date.today()) + start_date = fields.Date(string="生效日期", default=fields.Date.today()) + end_date = fields.Date(string="终止日期") + sequence = fields.Integer(string="顺序",compute='_compute_sequence',store=True) + employee_ids = fields.Many2many('hr.employee', string="人员范围") + department_ids = fields.Many2many('hr.department', string='部门范围') + users_role_ids = fields.Many2many('res.groups', string='角色范围') + users_role_group_ids = fields.Many2many('res.groups', string='角色组', relation='yuthon_notice_role_group_rel') + notice_state = fields.Selection([('no', '未发布'), ('yes', '已发布'), ('stop', '已终止')], default='no', string="公告状态") + documents_ids = fields.Many2many('ir.attachment', string="附件") + is_notice_roger = fields.Selection([('yes', '已读'), ('no', '未读')], default='no', string="确认状态") + is_change_notice = fields.Boolean(string="是否发送通知", default=True) + attn_notice = fields.Html(string="经办人意见") + notice_text = fields.Html(string="正文") + read_number = fields.Integer(string='阅读量', compute="_compute_read_number", store=True) + is_this_company = fields.Boolean(string='本公司', default=True) + approval_user_id = fields.Many2one('res.users', string="审核人") + approval_date = fields.Datetime(string="审核日期") + is_sticky = fields.Boolean(string="是否置顶") + urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', default='normal') + active = fields.Boolean('Active', default=True, tracking=True) + + work_end = fields.Boolean(string="结束") + users_ids = fields.Many2many( + 'res.users', + relation='yuthon_notice_users_rel', + column1='notice_id', + column2='user_id', + string='用户' + ) + self_users_ids = fields.Many2many( + 'res.users', + relation='yuthon_notice_current_users_rel', + column1='notice_id', + column2='user_id', + string="当前经办人", + store=True, + ) + + @api.model_create_multi + def create(self, vals_list): + for i in vals_list: + if i['department_ids'] == i['employee_ids'] == i['users_role_ids'] == i['users_role_group_ids']: + raise UserError('请填写发布的范围') + return super().create(vals_list) + + def _get_default_name_title(self): + return self._description if self._description else self._name + + name_title = fields.Char(default=_get_default_name_title) + + @api.constrains('employee_ids', 'department_ids', 'users_role_ids', 'users_role_group_ids') + def _constrains_employee_related_fields(self): + + def get_department_employees(department_id): + valid_user_ids = set() + employee_ids = self.env['hr.employee'].search([('department_id', '=', department_id.id)]) + valid_user_ids.update([emp.user_id.id for emp in employee_ids if emp.user_id]) + child_ids = self.env['hr.department'].search([('parent_id', '=', department_id.id)]) + for child in child_ids: + valid_user_ids.update(get_department_employees(child)) + return valid_user_ids + + for notice in self: + if notice.env.context.get('skip_constrains'): + continue + + department_user_ids = set() + for department_id in notice.department_ids: + department_user_ids.update(get_department_employees(department_id)) + group_role_ids = notice.users_role_group_ids + all_users = set( + notice.employee_ids.mapped('user_id').ids + + group_role_ids.mapped('users').ids + + notice.users_role_ids.mapped('users').ids + + list(department_user_ids) + ) + notice.with_context(skip_constrains=True).users_ids = [(6, 0, list(all_users))] + + def get_no_read_count(self): + return self.search_count([('is_notice_roger', '=', 'no'), ('notice_state', '=', 'yes')]) + + def read(self, fields=None, load='_classic_read'): + for con in self: + con_id = self.env['yuthon.confirm.users'].search([('notice_id', '=', con.id), ('employee_id', '=', con.env.user.employee_id.id)]) + if con_id.employee_id == con.env.user.employee_id: + con.is_notice_roger = 'yes' + con_id.write({'state': 'yes'}) + return super(YuthonNotice, self).read(fields=fields, load=load) + + @api.depends('read_number') + def _compute_read_number(self): + notice_confirm_ids = self.env['yuthon.confirm.users'].search( + [('notice_id.id', '=', self.id), ('state', '=', 'yes')]) + for read in self: + if notice_confirm_ids: + read.read_number = len(notice_confirm_ids) + else: + read.read_number = 0 + + def on_sequence(self): + self.sequence = 1 + + @api.onchange('is_sticky') + def _compute_sequence(self): + for i in self: + if i.is_sticky: + i.sequence = 1 + else: + i.sequence = 0 + + def no_sequence(self): + self.sequence = 0 + + def notice_publish(self): + if self.is_change_notice: + conf_old = self.env['yuthon.confirm.users'].search([('notice_id', '=', self.id), ('employee_id', 'in', self.users_ids.ids)]) + for user_id in self.users_ids: + url = f'http://oa.thtzjt.com/webh5#/notice?id={self.id}' + if conf_old: + for co in conf_old: + co.write({'state': 'no'}) + else: + self.env['yuthon.confirm.users'].create({ + 'notice_id': self.id, + 'employee_id': user_id.employee_ids.id, + 'notice_code': self.code, + 'notice_name': self.name, + 'state': 'no', + }) + we_id = self.env['hr.employee'].search([('id', '=', user_id.employee_ids.id)]).wecom_userid + self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=we_id, title="通知公告", + description=self.name + '发布人:' + self.users_id.name, + url=url, btntxt="详细信息") + self.write({"notice_state": 'yes'}) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'type': 'success', + 'message': '通知公告已经发布', + 'sticky': False, + } + } + else: + raise UserError('该公告不允许通知,请勾选通知') + + + def look_up_notice_users(self): + return { + "type": "ir.actions.act_window", + "name": "通知公告确认信息", + "domain": [("notice_id", "=", self.id)], + "res_model": "yuthon.confirm.users", + "view_mode": "tree", + "context": { + "search_default_group_state": True, + }, + } + + def stop_notice(self): + self.write({ + 'notice_state': 'stop', + 'end_date': fields.Date.today(), + }) + + +class YuthonCsTree(models.Model): + _name = 'yuthon.cs.tree' + _description = 'Yuthon Customer Service Tree' + + name = fields.Char(string='Name', required=True) + + def _compute_sequence(self): + for record in self: + record.sequence = 1 + + + + +# 工作流的指定申请人逻辑? +# 转交的时候选择条件,进行优化 + +# 报表合同和产品业务 +# 企业微信等三方平台框架--和工作流 \ No newline at end of file diff --git a/yuthon_notice/models/yuthon_notice_type.py b/yuthon_notice/models/yuthon_notice_type.py new file mode 100644 index 0000000..abe2295 --- /dev/null +++ b/yuthon_notice/models/yuthon_notice_type.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, tools +from odoo.exceptions import ValidationError, UserError + + +class YuthonNoticeType(models.Model): + _name = "yuthon.notice.type" + _description = "通知公告类型管理" + + name = fields.Char(string="类型名称") + note = fields.Char(string="说明") diff --git a/yuthon_notice/security/ir.model.access.csv b/yuthon_notice/security/ir.model.access.csv new file mode 100644 index 0000000..fd6f27c --- /dev/null +++ b/yuthon_notice/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_yuthon_notice,yuthon_notice,model_yuthon_notice,,1,1,1,1 +access_yuthon_notice_type,yuthon_notice_type,model_yuthon_notice_type,base.group_user,1,1,1,1 +access_yuthon_confirm_users,yuthon_confirm_users,model_yuthon_confirm_users,base.group_user,1,1,1,1 +access_yuthon_cs_tree,yuthon_cs_tree,model_yuthon_cs_tree,base.group_user,1,1,1,1 + diff --git a/yuthon_notice/static/description/icon.png b/yuthon_notice/static/description/icon.png new file mode 100644 index 0000000..c4f2d4d Binary files /dev/null and b/yuthon_notice/static/description/icon.png differ diff --git a/yuthon_notice/static/scss/notice_scss.scss b/yuthon_notice/static/scss/notice_scss.scss new file mode 100644 index 0000000..0bcc2b2 --- /dev/null +++ b/yuthon_notice/static/scss/notice_scss.scss @@ -0,0 +1,5 @@ + +.list_ids_width{ + width: 80px !important; + max-width: 80px !important; +} \ No newline at end of file diff --git a/yuthon_notice/static/src/js/custom_create_button.js b/yuthon_notice/static/src/js/custom_create_button.js new file mode 100644 index 0000000..3a586d1 --- /dev/null +++ b/yuthon_notice/static/src/js/custom_create_button.js @@ -0,0 +1,29 @@ +/** @odoo-module */ +import { ListController } from "@web/views/list/list_controller"; +import { registry } from '@web/core/registry'; +import { listView } from '@web/views/list/list_view'; + +export class CustomNoticeListController extends ListController { + setup() { + super.setup(); + } + openNoticeForm() { + this.actionService.doAction({ + type: 'ir.actions.act_window', + res_model: 'yuthon.notice', + name: '创建通知公告', + view_mode: 'form', + view_type: 'form', + views: [[false, 'form']], // 让 Odoo 找到默认的 form 视图 + target: 'current', // 在当前窗口打开 + context: {}, + }); + } +} + +registry.category("views").add("custom_notice_tree_button", { + ...listView, + Controller: CustomNoticeListController, + buttonTemplate: "yuthon_notice.ListView.Buttons", +}); + diff --git a/yuthon_notice/static/src/xml/custom_create_button.xml b/yuthon_notice/static/src/xml/custom_create_button.xml new file mode 100644 index 0000000..6225152 --- /dev/null +++ b/yuthon_notice/static/src/xml/custom_create_button.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/yuthon_notice/views/yuthon_confirm_users_views.xml b/yuthon_notice/views/yuthon_confirm_users_views.xml new file mode 100644 index 0000000..6cc06e4 --- /dev/null +++ b/yuthon_notice/views/yuthon_confirm_users_views.xml @@ -0,0 +1,38 @@ + + + + yuthon.confirm.users.tree + yuthon.confirm.users + + +
+
+ + + + +
+
+
+ + + yuthon.confirm.users.search + yuthon.confirm.users + + + + + + + + + + + 公告确认信息 + yuthon.confirm.users + list + + +
\ No newline at end of file diff --git a/yuthon_notice/views/yuthon_cs_tree_views.xml b/yuthon_notice/views/yuthon_cs_tree_views.xml new file mode 100644 index 0000000..eec7a0e --- /dev/null +++ b/yuthon_notice/views/yuthon_cs_tree_views.xml @@ -0,0 +1,17 @@ + + + + yuthon.cs.tree + yuthon.cs.tree + + + + + + + + Yuthon CS Tree + yuthon.cs.tree + list + + \ No newline at end of file diff --git a/yuthon_notice/views/yuthon_notice_type_views.xml b/yuthon_notice/views/yuthon_notice_type_views.xml new file mode 100644 index 0000000..5e0b009 --- /dev/null +++ b/yuthon_notice/views/yuthon_notice_type_views.xml @@ -0,0 +1,33 @@ + + + + yuthon.notice.type.tree + yuthon.notice.type + + + + + + + + + + + 公告类型管理 + yuthon.notice.type + list + + + + + + + \ No newline at end of file diff --git a/yuthon_notice/views/yuthon_notice_views.xml b/yuthon_notice/views/yuthon_notice_views.xml new file mode 100644 index 0000000..b206c5b --- /dev/null +++ b/yuthon_notice/views/yuthon_notice_views.xml @@ -0,0 +1,216 @@ + + + + yuthon.notice.tree + yuthon.notice + + + + + + + + + + + + + + diff --git a/yuthon_will_task/views/yuthon_dates_task_views.xml b/yuthon_will_task/views/yuthon_dates_task_views.xml new file mode 100644 index 0000000..7a3c170 --- /dev/null +++ b/yuthon_will_task/views/yuthon_dates_task_views.xml @@ -0,0 +1,38 @@ + + + + yuthon.dates.task.tree + yuthon.dates.task + + + + + + + + + + + yuthon.dates.task.search + yuthon.dates.task + + + + + + + + + + + 日程任务 + yuthon.dates.task + tree + + + {'search_default_task_state':1} + + diff --git a/yuthon_will_task/views/yuthon_task_menu.xml b/yuthon_will_task/views/yuthon_task_menu.xml new file mode 100644 index 0000000..7a54cb0 --- /dev/null +++ b/yuthon_will_task/views/yuthon_task_menu.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/yuthon_will_task/views/yuthon_task_time_views.xml b/yuthon_will_task/views/yuthon_task_time_views.xml new file mode 100644 index 0000000..5ddad4e --- /dev/null +++ b/yuthon_will_task/views/yuthon_task_time_views.xml @@ -0,0 +1,21 @@ + + + + yuthon.task.time.tree + yuthon.task.time + + + + + + + + + + + + 提醒时长配置 + yuthon.task.time + tree + + diff --git a/yuthon_will_task/views/yuthon_will_task_views.xml b/yuthon_will_task/views/yuthon_will_task_views.xml new file mode 100644 index 0000000..a47e339 --- /dev/null +++ b/yuthon_will_task/views/yuthon_will_task_views.xml @@ -0,0 +1,371 @@ + + + + yuthon.will.task.tree + yuthon.will.task + + + + + + + + + + + +