Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87e8f0c636 | ||
|
|
8ea8b089af | ||
|
|
dfa665bbde | ||
|
|
8939e61094 | ||
|
|
0a96d0e868 | ||
|
|
ab62d0e79b | ||
|
|
d84c482c84 | ||
|
|
7b393414b3 | ||
|
|
fa5b2c649e | ||
|
|
27b17aeaa6 | ||
|
|
246326ed9c | ||
|
|
c84da2ed10 | ||
|
|
9d5a60af09 | ||
|
|
d73bd82769 | ||
|
|
d1a217f8cf | ||
|
|
0d6d3e0bb9 | ||
|
|
4cdbaf9799 | ||
|
|
84df154ce2 | ||
|
|
63c87c7e56 | ||
|
|
ba32af9e04 | ||
|
|
07c29e451d | ||
|
|
194594ffa5 | ||
|
|
2b89ca6cb5 | ||
|
|
013aa37ebc | ||
|
|
9f909e4833 | ||
|
|
544de24893 | ||
|
|
1b6ea810b2 | ||
|
|
f75aca9fd5 | ||
|
|
aa07c87bbb | ||
|
|
0bcbba4383 | ||
|
|
ad7bc5877d | ||
|
|
2b186521ff | ||
|
|
47dc1c428f | ||
|
|
f5967ae13d | ||
|
|
40a9ed9bd1 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': '教育',
|
||||
'version': '18.0.3.0.4',
|
||||
'version': '18.0.3.0.7',
|
||||
'summary': 'Odoo 18 教学管理平台 — 师生管理 + 课程管理 + 作业/进展(老师·学生双角色)',
|
||||
'description': """
|
||||
Odoo 18 教学管理平台(面向老师与学生,双角色权限)
|
||||
@@ -58,6 +58,12 @@
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'college/static/src/scss/college_theme.scss',
|
||||
'college/static/src/scss/hide_about_edit.scss',
|
||||
'college/static/src/js/hide_company_menu.js',
|
||||
'college/static/src/js/hide_user_menu_items.js',
|
||||
'college/static/src/js/hide_activity_menu.js',
|
||||
'college/static/src/js/portal_indicator.js',
|
||||
'college/static/src/xml/portal_indicator.xml',
|
||||
],
|
||||
'web.assets_web_dark': [
|
||||
'college/static/src/scss/college_theme_dark.scss',
|
||||
|
||||
@@ -236,4 +236,63 @@
|
||||
</div>
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ===================== 团队介绍 ===================== -->
|
||||
<record id="about_page_team" model="yuthon.about.page">
|
||||
<field name="name">团队介绍</field>
|
||||
<field name="category">team</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="active">True</field>
|
||||
<field name="content"><![CDATA[
|
||||
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif;color:#fff;line-height:1.65;">
|
||||
<!-- 顶部 banner:深蓝科技风 -->
|
||||
<div style="background:linear-gradient(135deg,#0f172a 0%,#1e40af 50%,#0ea5e9 100%);padding:72px 32px 56px;text-align:center;position:relative;overflow:hidden;">
|
||||
<!-- 装饰圆 -->
|
||||
<div style="position:absolute;top:-60px;left:-60px;width:220px;height:220px;background:rgba(255,255,255,0.06);border-radius:50%;"></div>
|
||||
<div style="position:absolute;bottom:-40px;right:-40px;width:180px;height:180px;background:rgba(255,255,255,0.05);border-radius:50%;"></div>
|
||||
|
||||
<!-- 顶部胶囊标签 -->
|
||||
<div style="display:inline-flex;align-items:center;background:rgba(255,255,255,0.10);border:1px solid rgba(255,255,255,0.18);border-radius:999px;padding:6px 18px 6px 8px;margin-bottom:28px;position:relative;z-index:1;">
|
||||
<span style="display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;background:#facc15;color:#0f172a;border-radius:8px;font-weight:800;font-size:14px;margin-right:10px;">P</span>
|
||||
<span style="font-size:14px;color:#e2e8f0;letter-spacing:0.5px;">Peng Yuthon</span>
|
||||
</div>
|
||||
|
||||
<h1 style="font-size:42px;font-weight:800;margin:0 0 16px;color:#fff !important;position:relative;z-index:1;">鹏程万里 气宇轩昂</h1>
|
||||
<p style="font-size:18px;margin:0 0 36px;color:#cbd5e1;position:relative;z-index:1;">软件 · 硬件 · AI 智能体</p>
|
||||
|
||||
<!-- 底部胶囊标签 -->
|
||||
<div style="display:flex;flex-wrap:wrap;justify-content:center;gap:12px;position:relative;z-index:1;">
|
||||
<span style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:999px;padding:8px 18px;font-size:14px;color:#e2e8f0;">软件</span>
|
||||
<span style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:999px;padding:8px 18px;font-size:14px;color:#e2e8f0;">硬件</span>
|
||||
<span style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:999px;padding:8px 18px;font-size:14px;color:#e2e8f0;">AI 智能体</span>
|
||||
<span style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:999px;padding:8px 18px;font-size:14px;color:#e2e8f0;">工程化教育</span>
|
||||
<span style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:999px;padding:8px 18px;font-size:14px;color:#e2e8f0;">企业实训</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div style="padding:36px 28px;background:#fff;color:#334155;">
|
||||
<h2 style="color:#0f172a;font-size:20px;margin:0 0 16px;">关于我们</h2>
|
||||
<p style="margin:0 0 28px;font-size:15px;color:#475569;">
|
||||
Peng Yuthon 专注于软件、硬件与 AI 智能体方向的工程化教育,
|
||||
致力于为高校与职业院校提供真实企业场景驱动的实训解决方案。
|
||||
</p>
|
||||
|
||||
<h2 style="color:#0f172a;font-size:20px;margin:0 0 16px;">联系方式</h2>
|
||||
<ul style="list-style:none;padding:0;margin:0;font-size:15px;line-height:2;color:#475569;">
|
||||
<li><b style="color:#0f172a;">B 站:</b>bilibili: pengyuthon</li>
|
||||
<li><b style="color:#0f172a;">邮箱:</b>pengyuthon@163.com</li>
|
||||
<li><b style="color:#0f172a;">官网:</b>pengyuthon.cn</li>
|
||||
<li><b style="color:#0f172a;">学习平台:</b>edu.pengyuthon.cn</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 版权页脚 -->
|
||||
<div style="padding:18px 24px;background:#0f172a;color:#94a3b8;font-size:13px;text-align:center;">
|
||||
© 2026 Peng Yuthon. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
<field name="end_date">2026-01-15</field>
|
||||
<field name="introduction"><![CDATA[<p>面向零基础开发者,系统讲解 Odoo 18 的模块结构、模型(Model)、视图(View)与控制器(Controller),带你跑通第一个自定义模块。</p>]]></field>
|
||||
<field name="objectives"><![CDATA[<ul><li>理解 Odoo 的 MVC 架构</li><li>能独立创建并安装一个模块</li><li>掌握模型字段与关系(M2O / O2M / M2M)</li></ul>]]></field>
|
||||
<field name="syllabus"><![CDATA[<ol><li>开发环境搭建</li><li>模块清单与目录结构</li><li>模型定义与字段类型</li><li>视图(list / form / kanban)</li><li>菜单与权限(ACL)</li></ol>]]></field>
|
||||
<field name="features"><![CDATA[<p>配套视频讲解 + 可运行示例代码,支持线下实训环境动手练习。</p>]]></field>
|
||||
<field name="student_ids" eval="[(6, 0, [ref('student_wang'), ref('student_01'), ref('student_02'), ref('student_03')])]"/>
|
||||
<field name="state">published</field>
|
||||
@@ -102,7 +101,6 @@
|
||||
<field name="end_date">2026-06-30</field>
|
||||
<field name="introduction"><![CDATA[<p>深入 Odoo 的视图体系:列表、表单、看板、搜索、图形,以及 CSS 与 JS 资产定制。</p>]]></field>
|
||||
<field name="objectives"><![CDATA[<ul><li>掌握 list/form/kanban 视图语法</li><li>能用 inherit 扩展已有视图</li><li>理解静态资源(assets)加载机制</li></ul>]]></field>
|
||||
<field name="syllabus"><![CDATA[<ol><li>视图类型总览</li><li>视图继承与 xpath</li><li>看板与搜索视图</li><li>前端资产与主题定制</li></ol>]]></field>
|
||||
<field name="features"><![CDATA[<p>视频 + PPT 双形态,含实操演练。</p>]]></field>
|
||||
<field name="student_ids" eval="[(6, 0, [ref('student_02'), ref('student_04')])]"/>
|
||||
<field name="state">draft</field>
|
||||
@@ -120,7 +118,6 @@
|
||||
<field name="end_date">2027-01-10</field>
|
||||
<field name="introduction"><![CDATA[<p>以 PPT 讲义为主,讲解 QWeb 报表、PDF 打印与数据导出。</p>]]></field>
|
||||
<field name="objectives"><![CDATA[<ul><li>掌握 QWeb 报表模板</li><li>能定制 PDF 打印</li></ul>]]></field>
|
||||
<field name="syllabus"><![CDATA[<ol><li>报表模型与 action</li><li>QWeb 模板语法</li><li>打印与导出</li></ol>]]></field>
|
||||
<field name="features"><![CDATA[<p>适合线下培训,讲师直接分发 PPT。</p>]]></field>
|
||||
<field name="state">draft</field>
|
||||
</record>
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
23=Fleet(车辆)
|
||||
如需保留/恢复某类目,从下面列表中删除对应 id 即可(注意总数会变化)。 -->
|
||||
<field name="domain">[('application', '=', True), ('category_id', 'not in', [13, 54, 71, 15, 94, 6, 10, 11, 20, 33, 55, 37, 59, 21, 89, 90, 92, 103, 28, 32, 88, 23])]</field>
|
||||
<field name="context">{}</field>
|
||||
<!-- shixun_hide_empty_categories:搜索面板「类别」列过滤掉计数为 0 的类别
|
||||
(见 models/ir_module_module_info.py 对 search_panel_select_range 的包装) -->
|
||||
<field name="context">{'shixun_hide_empty_categories': True}</field>
|
||||
<field name="search_view_id" ref="shixun_module_search_view"/>
|
||||
<field name="limit">50</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * college
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 18.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-09-21 11:31:00+0000\n"
|
||||
"PO-Revision-Date: 2026-09-21 11:31:00+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Language: zh_CN\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_education
|
||||
msgid "Education"
|
||||
msgstr "教育"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_game
|
||||
msgid "Game"
|
||||
msgstr "游戏"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_backend
|
||||
msgid "Backend"
|
||||
msgstr "后端"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_themes
|
||||
msgid "Themes"
|
||||
msgstr "主题"
|
||||
|
||||
# 以下四项 base 自带翻译已存在,这里补一份,
|
||||
# 保证 college 模块单独升级时这几个类别也一定是中文。
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_tools
|
||||
#: model:ir.module.category,name:base.module_category_hidden_tools
|
||||
msgid "Tools"
|
||||
msgstr "工具"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_uncategorized
|
||||
msgid "Uncategorized"
|
||||
msgstr "未分类"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_localization
|
||||
msgid "Localization"
|
||||
msgstr "本地化"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.category,name:base.module_category_manufacturing_maintenance
|
||||
msgid "Maintenance"
|
||||
msgstr "维修"
|
||||
@@ -18,6 +18,7 @@ class YuthonAboutPage(models.Model):
|
||||
('operation', '操作手册'),
|
||||
('product', '产品介绍'),
|
||||
('import', '导入说明'),
|
||||
('team', '团队介绍'),
|
||||
], string='分类', default='system', required=True)
|
||||
content = fields.Html(string='内容', sanitize=True, translate=True)
|
||||
sequence = fields.Integer(string='排序', default=10)
|
||||
|
||||
@@ -20,6 +20,13 @@ class SchoolAssignment(models.Model):
|
||||
ondelete='cascade',
|
||||
domain="[('state', '=', 'published'), ('teacher_id', '=', uid)]",
|
||||
help='只能选择自己已发布的课程。')
|
||||
chapter_id = fields.Many2one(
|
||||
'yuthon.school.course.chapter',
|
||||
string='对应章节',
|
||||
domain="[('course_id', '=', course_id)]",
|
||||
ondelete='set null',
|
||||
index=True,
|
||||
help='该作业所属的课程章节,不选则代表整门课程作业。')
|
||||
teacher_id = fields.Many2one(
|
||||
'res.users', string='布置教师',
|
||||
default=lambda self: self.env.user, required=True)
|
||||
@@ -111,6 +118,15 @@ class SchoolAssignmentSubmission(models.Model):
|
||||
_order = 'submit_time desc, id'
|
||||
_rec_name = 'name'
|
||||
|
||||
chapter_id = fields.Many2one(
|
||||
'yuthon.school.course.chapter',
|
||||
string='章节',
|
||||
related='assignment_id.chapter_id',
|
||||
store=True, index=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
name = fields.Char(string='提交标题', readonly=True, copy=False)
|
||||
assignment_id = fields.Many2one(
|
||||
'yuthon.school.assignment', string='作业', required=True, ondelete='cascade')
|
||||
|
||||
@@ -75,6 +75,7 @@ class SchoolCourse(models.Model):
|
||||
introduction = fields.Html(string='课程介绍', sanitize=True)
|
||||
objectives = fields.Html(string='课程目标', sanitize=True)
|
||||
features = fields.Html(string='课程特色', sanitize=True)
|
||||
syllabus = fields.Html(string='课程大纲', sanitize=True)
|
||||
business_scenario = fields.Html(string='业务情景', sanitize=True)
|
||||
|
||||
# 学员分配
|
||||
@@ -105,6 +106,20 @@ class SchoolCourse(models.Model):
|
||||
chapter_count = fields.Integer(
|
||||
string='章节数', compute='_compute_chapter_count', store=True)
|
||||
|
||||
# 课程作业:一门课对应多条作业记录
|
||||
assignment_ids = fields.One2many(
|
||||
'yuthon.school.assignment',
|
||||
'course_id',
|
||||
string='作业布置',
|
||||
)
|
||||
|
||||
# 学习进展:一门课对应多条学员进度记录
|
||||
progress_ids = fields.One2many(
|
||||
'yuthon.school.progress',
|
||||
'course_id',
|
||||
string='学习进展',
|
||||
)
|
||||
|
||||
@api.depends('student_ids')
|
||||
def _compute_student_count(self):
|
||||
for record in self:
|
||||
|
||||
@@ -48,6 +48,21 @@ class IrModuleModule(models.Model):
|
||||
help='在「模块信息 / 知识点」页展示的核心概念与使用要点(由 college 维护)',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def search_panel_select_range(self, field_name, **kwargs):
|
||||
"""搜索面板类别列:按 context 开关过滤掉计数为 0 的顶层类别。
|
||||
|
||||
背景:base 对 ir.module.module 的 search_panel_select_range 做了重写
|
||||
(ir_module.py),只要顶层类别的子树里存在任何模块就会返回该类别,
|
||||
即使它名下没有一条记录匹配当前搜索域(计数为 0、界面上不显示数字)。
|
||||
「实训模块」动作通过 context 开关 shixun_hide_empty_categories 启用本过滤,
|
||||
不影响原生「应用」页面;hierarchize/expand 等视图属性在该重写分支中不生效。
|
||||
"""
|
||||
res = super().search_panel_select_range(field_name, **kwargs)
|
||||
if field_name == 'category_id' and self.env.context.get('shixun_hide_empty_categories'):
|
||||
res['values'] = [v for v in res.get('values', []) if v.get('__count')]
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def populate_module_info(self):
|
||||
"""为 school 自建模块写入「功能亮点 / 模块介绍」文案。
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 隐藏导航栏右侧的「活动」图标(日历+时钟那个,点击弹出"我的活动"下拉)
|
||||
*
|
||||
* 原理:导航栏右侧的系统托盘 (systray) 是一个组件注册表,
|
||||
* mail 模块在加载时把活动菜单注册了进去(见
|
||||
* odoo18/addons/mail/static/src/core/web/activity_menu.js 最后一行
|
||||
* registry.category("systray").add("mail.activity_menu", ...))。
|
||||
* 这里在资源包加载阶段把它从注册表里摘掉,导航栏就不会渲染它。
|
||||
*
|
||||
* 如需恢复:删除本文件并把 __manifest__.py assets 里对应的一行去掉即可。
|
||||
*
|
||||
* 注意:本文件改动需要「升级模块」才会生效(-d <库名> -u college),
|
||||
* 并强刷浏览器 (Ctrl+Shift+R)。
|
||||
*/
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("systray").remove("mail.activity_menu");
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 隐藏导航栏「公司切换器」(YourCompany 那一块)
|
||||
*
|
||||
* 原理:Odoo 导航栏右侧的系统托盘 (systray) 是一个组件注册表,
|
||||
* web 模块在启动时把 SwitchCompanyMenu 注册了进去(见
|
||||
* odoo18/addons/web/static/src/webclient/switch_company_menu/switch_company_menu.js
|
||||
* 最后一行 registry.category("systray").add(...))。
|
||||
* 这里在资源包加载阶段把它从注册表里摘掉,导航栏就不会渲染它。
|
||||
*
|
||||
* 单公司场景下该组件无意义;如需恢复,删除本文件并把
|
||||
* __manifest__.py assets 里对应的一行去掉即可。
|
||||
*
|
||||
* 注意:本文件改动需要「升级模块 + 重启」才会生效
|
||||
* (-d <库名> -u college 重启主进程),并强刷浏览器 (Ctrl+Shift+R)。
|
||||
*/
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("systray").remove("SwitchCompanyMenu");
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 右上角头像下拉菜单:只保留「退出登录」
|
||||
*
|
||||
* 原理:Odoo 前端把头像菜单的每个条目注册到
|
||||
* registry.category("user_menuitems") 里(web / web_tour / hr /
|
||||
* yuthon_base 的 udoo_om_ux、quick_navigation 等模块都会往里加)。
|
||||
* 这里在资源包加载阶段把白名单之外的条目全部摘掉,并监听 UPDATE 事件,
|
||||
* 保证晚于本文件注册的条目(例如以后新装的模块)也会被自动移除,
|
||||
* 因此不用关心资源加载顺序。
|
||||
*
|
||||
* 被移除的条目一览(想恢复哪个,把它的 key 加进 KEEP 即可):
|
||||
* documentation 文档 (web)
|
||||
* support 支持 (web)
|
||||
* shortcuts 快捷键 (web)
|
||||
* profile 偏好 (web / hr)
|
||||
* odoo_account Odoo.com 账户 (web)
|
||||
* install_pwa 安装应用 (web)
|
||||
* separator 分隔线 (web)
|
||||
* web_tour.tour_enabled 新手上路 (web_tour)
|
||||
* appearance 外观 (udoo_om_ux)
|
||||
* separator42 分隔线 (udoo_om_ux)
|
||||
* quick_menu_config 快捷菜单配置 (quick_navigation)
|
||||
*
|
||||
* 注意:本文件改动需要「升级模块 + 重启」才会生效
|
||||
* (-d <库名> -u college),并强刷浏览器 (Ctrl+Shift+R)。
|
||||
*/
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const KEEP = new Set(["log_out"]);
|
||||
const userMenuRegistry = registry.category("user_menuitems");
|
||||
|
||||
/**
|
||||
* 移除所有不在白名单里的菜单项。
|
||||
* 用 getEntries() 先取快照再删,避免边遍历边删。
|
||||
*/
|
||||
function pruneUserMenu() {
|
||||
const keys = userMenuRegistry.getEntries().map(([key]) => key);
|
||||
for (const key of keys) {
|
||||
if (!KEEP.has(key)) {
|
||||
userMenuRegistry.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1) 清理已经注册过的条目
|
||||
pruneUserMenu();
|
||||
|
||||
// 2) 监听后续新增:只处理 add,不处理 delete,避免自己触发自己造成递归。
|
||||
// 注意:Owl 的 EventBus 继承自 EventTarget,trigger 用 CustomEvent 派发,
|
||||
// 监听函数收到的是事件对象,注册表传的 payload 在 ev.detail 里。
|
||||
userMenuRegistry.addEventListener("UPDATE", (ev) => {
|
||||
const { operation, key } = ev.detail || {};
|
||||
if (operation === "add" && !KEEP.has(key)) {
|
||||
userMenuRegistry.remove(key);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
import { user } from "@web/core/user";
|
||||
import { Component, onWillStart, useState } from "@odoo/owl";
|
||||
|
||||
export class PortalIndicator extends Component {
|
||||
static template = "college.PortalIndicator";
|
||||
|
||||
setup() {
|
||||
this.state = useState({ label: "" });
|
||||
onWillStart(async () => {
|
||||
// 顺序:管理员 -> 老师 -> 学生(高权限优先)
|
||||
if (await user.hasGroup("base.group_system")) {
|
||||
this.state.label = "管理员端";
|
||||
} else if (await user.hasGroup("college.group_teacher")) {
|
||||
this.state.label = "教师端";
|
||||
} else if (await user.hasGroup("college.group_student")) {
|
||||
this.state.label = "学生端";
|
||||
} else {
|
||||
this.state.label = "用户端";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// sequence 越小越靠右(user_menu 是 0)。想贴最右边用负数。
|
||||
registry.category("systray").add(
|
||||
"college.PortalIndicator",
|
||||
{ Component: PortalIndicator },
|
||||
{ sequence: 1}
|
||||
);
|
||||
@@ -267,3 +267,26 @@
|
||||
p, li { color: $o-main-text-color; }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 隐藏右上角「公司切换器」(我的学校)
|
||||
// 项目为单学校场景,不需要用户切换公司。
|
||||
// ============================================================
|
||||
.o_switch_company_menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
// 角色提示徽章:避免被 Odoo 导航栏 .badge 通知样式影响
|
||||
.o_college_portal_indicator {
|
||||
align-self: center; // 在 systray 中垂直居中,不被拉伸/裁切
|
||||
}
|
||||
|
||||
.o_portal_badge {
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.7em;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.2;
|
||||
color: black;
|
||||
white-space: nowrap; // 不换行
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// 关于页(yuthon.about.page)为只读展示页:
|
||||
// form 视图已设 edit="false",但教师组有该模型写权限时控制面板仍会渲染「编辑」按钮,
|
||||
// 这里用 CSS 精准隐藏该页面的编辑按钮(作用域限定 o_about_full_form,不影响其他表单)。
|
||||
.o_form_view.o_about_full_form .o_form_button_edit,
|
||||
.o_form_view.o_about_full_form button[data-hotkey="e"] {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="college.PortalIndicator"
|
||||
class="o_college_portal_indicator d-flex align-items-center ms-2">
|
||||
<span class="o_portal_badge" t-esc="state.label"/>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
侧边栏窄模式悬浮提示
|
||||
|
||||
背景:udoo_om_ux 左侧导航栏(.o_sidenav)拖窄 / 切换窄模式(宽度 < 142 时加 .sm)
|
||||
后,菜单文字 .nav-title 被 display:none 隐藏、只剩图标,而原生 title 属性恰好在
|
||||
被隐藏的 span 上,导致鼠标悬停看不到任何菜单名提示。
|
||||
|
||||
做法:继承 wub.NavBar.AppsMenu,给每个菜单外壳 div.nav-item 挂 Odoo 原生
|
||||
data-tooltip(黑色气泡,与系统其它提示一致),只在窄模式输出。
|
||||
|
||||
⚠ 为什么必须挂在外壳 div 上,而不是里面的 DropdownItem(踩过的坑):
|
||||
Odoo 在开发者模式(URL 带 ?debug=1,debug 值会存进 session)下,模板继承的
|
||||
applyInheritance() 会给每个被修改的元素「前面」插入一个调试注释节点
|
||||
(见 odoo/addons/web/static/src/core/template_inheritance.js:260)。
|
||||
- 如果改的是 .nav-item 的子元素,这个注释就成了 .nav-item 的 firstChild;
|
||||
而主题 OmuxNavBar.preSubnavTiming 里写死了
|
||||
evTarget.firstChild.classList.contains('on')
|
||||
注释节点没有 classList → 报
|
||||
TypeError: Cannot read properties of undefined (reading 'contains')
|
||||
- 改外壳后,注释落在 .sidenav_cont 里当兄弟节点:主题其它地方用的是
|
||||
element.children[...](只数元素,忽略注释),完全不受影响,悬停范围还是整行。
|
||||
|
||||
想改气泡方向把下面的 'right' 改成 'bottom' 即可。
|
||||
-->
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="wub.NavBar.AppsMenu" t-inherit-mode="extension">
|
||||
<!-- 应用列表项(t-foreach 渲染出来的那一批) -->
|
||||
<xpath expr="//div[hasclass('nav-item')][@t-foreach]" position="attributes">
|
||||
<attribute name="t-att-data-tooltip">ue.sidenavWidth < 142 ? (app.label or app.name) : false</attribute>
|
||||
<attribute name="data-tooltip-position">right</attribute>
|
||||
</xpath>
|
||||
<!-- 收藏夹里「当前应用不在收藏中」时额外显示的那一项 -->
|
||||
<xpath expr="//div[hasclass('nav-item')][@t-if][not(@t-foreach)]" position="attributes">
|
||||
<attribute name="t-att-data-tooltip">ue.sidenavWidth < 142 ? (currentApp.label or currentApp.name) : false</attribute>
|
||||
<attribute name="data-tooltip-position">right</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -7,7 +7,8 @@
|
||||
<field name="name">yuthon.about.page.form</field>
|
||||
<field name="model">yuthon.about.page</field>
|
||||
<field name="arch" type="xml">
|
||||
<form class="o_about_full_form">
|
||||
<!-- create/edit 关掉:关于页内容由代码预置维护,页面右上角不再显示「创建/编辑」按钮 -->
|
||||
<form class="o_about_full_form" create="false" edit="false">
|
||||
<field name="content" widget="html" readonly="1" class="o_about_full"/>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
invisible="[('state', '!=', 'published')]"/>
|
||||
<button name="action_to_draft" type="object" string="转为草稿"
|
||||
invisible="[('state', '=', 'draft')]"/>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="draft,published,archived"/>
|
||||
<!-- <field name="state" widget="statusbar"-->
|
||||
<!-- statusbar_visible="draft,published,archived"/>-->
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
@@ -104,6 +104,23 @@
|
||||
<page string="业务情景">
|
||||
<field name="business_scenario"/>
|
||||
</page>
|
||||
|
||||
<page string="学习进度" groups="college.group_teacher,base.group_system">
|
||||
<field name="progress_ids" mode="kanban,list" readonly="1"
|
||||
context="{'kanban_view_ref': 'college.yuthon_school_progress_view_kanban', 'tree_view_ref': 'college.yuthon_school_progress_view_list'}"/>
|
||||
</page>
|
||||
<page string="作业布置" groups="college.group_teacher,base.group_system">
|
||||
<field name="assignment_ids">
|
||||
<list>
|
||||
<field name="name" string="作业标题"/>
|
||||
<field name="chapter_id" string="对应章节"/>
|
||||
<field name="due_date" string="截止日期"/>
|
||||
<field name="submission_count" string="提交数"/>
|
||||
<field name="state" string="状态"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
@@ -140,7 +157,6 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="课程内容">
|
||||
<field name="syllabus" readonly="1" string="课程大纲"/>
|
||||
<separator string="课程章节(按顺序学习视频与资料)"/>
|
||||
<field name="chapter_ids" readonly="1">
|
||||
<list>
|
||||
@@ -197,6 +213,10 @@
|
||||
<filter name="group_state" string="状态"
|
||||
context="{'group_by': 'state'}"/>
|
||||
</group>
|
||||
<searchpanel>
|
||||
<field name="course_type" string="课程模块" enable_counters="1"/>
|
||||
<field name="college" string="学院" enable_counters="1"/>
|
||||
</searchpanel>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -44,9 +44,13 @@
|
||||
<field name="name" placeholder="请输入作业标题"/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="course_id"/>
|
||||
<field name="chapter_id"
|
||||
domain="[('course_id', '=', course_id)]"
|
||||
context="{'default_course_id': course_id}"/>
|
||||
<field name="teacher_id" readonly="1"/>
|
||||
<field name="due_date"/>
|
||||
</group>
|
||||
@@ -55,6 +59,7 @@
|
||||
<field name="attachment_filename" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="作业说明">
|
||||
<field name="description"/>
|
||||
@@ -119,7 +124,7 @@
|
||||
<field name="name">yuthon.school.assignment.submission.list</field>
|
||||
<field name="model">yuthon.school.assignment.submission</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="作业提交" multi_edit="1">
|
||||
<list string="作业提交" multi_edit="1" create="0">
|
||||
<header>
|
||||
<button name="action_open_batch_wizard"
|
||||
type="object" string="批量批改"
|
||||
@@ -146,7 +151,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="state"
|
||||
default_order="due_date desc, id"
|
||||
class="college-course-kanban college-kanban-homework o_kanban_mobile">
|
||||
class="college-course-kanban college-kanban-homework o_kanban_mobile"
|
||||
create="0">
|
||||
<field name="assignment_id"/>
|
||||
<field name="student_id"/>
|
||||
<field name="score"/>
|
||||
|
||||
+42
-1
@@ -340,6 +340,20 @@ else:
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
|
||||
<!-- 团队介绍:打开预置记录 about_page_team -->
|
||||
<record id="action_yuthon_about_team" model="ir.actions.act_window">
|
||||
<field name="name">团队介绍</field>
|
||||
<field name="res_model">yuthon.about.page</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="res_id" ref="about_page_team"/>
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
<menuitem id="menu_yuthon_about_team"
|
||||
name="团队介绍"
|
||||
parent="menu_yuthon_about"
|
||||
action="action_yuthon_about_team"
|
||||
sequence="30"/>
|
||||
|
||||
<!-- 课程导入动作(关联 yuthon.school.course.import.wizard) -->
|
||||
<record id="action_yuthon_school_course_import_wizard" model="ir.actions.act_window">
|
||||
<field name="name">课程导入</field>
|
||||
@@ -388,8 +402,35 @@ else:
|
||||
</record>
|
||||
<menuitem id="menu_yuthon_about_question_import"
|
||||
name="题库导入"
|
||||
parent="menu_yuthon_exam_root"
|
||||
parent="menu_yuthon_exam_question"
|
||||
action="action_yuthon_about_question_import"
|
||||
sequence="60"
|
||||
groups="college.group_teacher,base.group_system"/>
|
||||
|
||||
<!-- ===================== 应用类别配置(挂在「实训模块」下) ===================== -->
|
||||
<!-- Odoo 默认只有 form 视图,没有 list 视图,需要先补一个 -->
|
||||
<record id="view_yuthon_module_category_list" model="ir.ui.view">
|
||||
<field name="name">ir.module.category.list (college)</field>
|
||||
<field name="model">ir.module.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="应用类别">
|
||||
<field name="name" string="名称"/>
|
||||
<field name="description" string="描述"/>
|
||||
<field name="sequence" string="排序"/>
|
||||
<field name="parent_id" string="上级应用"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_yuthon_module_category" model="ir.actions.act_window">
|
||||
<field name="name">应用类别配置</field>
|
||||
<field name="res_model">ir.module.category</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_yuthon_module_category"
|
||||
name="应用类别配置"
|
||||
parent="menu_shixun_module"
|
||||
action="action_yuthon_module_category"
|
||||
sequence="10"/>
|
||||
</odoo>
|
||||
|
||||
@@ -35,15 +35,7 @@ class CourseHomework(models.Model):
|
||||
teaching_class_id = fields.Many2one('course.teaching_class', string='教学班',
|
||||
ondelete='cascade', domain="[('course_id', '=', course_id)]")
|
||||
submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录')
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项目')
|
||||
=======
|
||||
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项')
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项')
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
|
||||
# ====================== 新增:自动计算提交通道是否开启 ======================
|
||||
@api.depends('state', 'deadline')
|
||||
@@ -90,8 +82,6 @@ class CourseHomework(models.Model):
|
||||
self.publish_time = fields.Datetime.now()
|
||||
# 1. 批量为本班学生生成空白未提交记录
|
||||
self._auto_create_empty_submit()
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
# 新增:自动生成对应作业的学生成绩明细
|
||||
self._auto_create_homework_score_detail()
|
||||
# 2. 推送作业通知给学生
|
||||
@@ -147,15 +137,7 @@ class CourseHomework(models.Model):
|
||||
|
||||
if detail_vals_list:
|
||||
self.env['course.student.score.detail'].create(detail_vals_list)
|
||||
=======
|
||||
# 2. 推送作业通知给学生
|
||||
self._send_homework_notice_to_students()
|
||||
|
||||
=======
|
||||
# 2. 推送作业通知给学生
|
||||
self._send_homework_notice_to_students()
|
||||
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
def _send_homework_notice_to_students(self):
|
||||
"""发布作业后推送消息给教学班学生"""
|
||||
self.ensure_one()
|
||||
@@ -184,10 +166,6 @@ class CourseHomework(models.Model):
|
||||
partner_ids=student_users.mapped('partner_id').ids,
|
||||
subtype_xmlid='mail.mt_comment'
|
||||
)
|
||||
<<<<<<< HEAD
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
|
||||
def _auto_create_empty_submit(self):
|
||||
"""仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录"""
|
||||
@@ -215,53 +193,12 @@ class CourseHomework(models.Model):
|
||||
batch_vals.append({
|
||||
'homework_id': self.id,
|
||||
'student_id': stu_id,
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
'course_score_item_id':self.course_score_item_id.id,
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
'state': 'unsubmit' # 默认未提交
|
||||
})
|
||||
# 批量创建空白提交记录
|
||||
submit_model.create(batch_vals)
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
def _send_homework_notice_to_students(self):
|
||||
"""发布作业后推送消息给教学班学生"""
|
||||
self.ensure_one()
|
||||
if not self.teaching_class_id:
|
||||
return
|
||||
# 获取当前教学班所有已选课学生
|
||||
enrolls = self.env['course.teaching_class.enrollment'].search([
|
||||
('teaching_class_id', '=', self.teaching_class_id.id),
|
||||
('state', '=', 'enrolled')
|
||||
])
|
||||
student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u)
|
||||
if not student_users:
|
||||
return
|
||||
# 消息内容
|
||||
subject = f"新作业发布:{self.name}"
|
||||
body = f"""
|
||||
<p>课程:{self.course_id.name}</p>
|
||||
<p>作业名称:{self.name}</p>
|
||||
<p>截止时间:{self.deadline or '无'}</p>
|
||||
<p>作业要求:{self.requirement or '无'}</p>
|
||||
"""
|
||||
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转)
|
||||
self.message_post(
|
||||
subject=subject,
|
||||
body=body,
|
||||
partner_ids=student_users.mapped('partner_id').ids,
|
||||
subtype_xmlid='mail.mt_note'
|
||||
)
|
||||
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
# ====================== 草稿/关闭作业 ======================
|
||||
def action_draft(self):
|
||||
self.state = 'draft'
|
||||
@@ -388,21 +325,14 @@ class CourseHomework(models.Model):
|
||||
def create(self, vals):
|
||||
homework_record = super().create(vals)
|
||||
# 草稿状态不执行自动生成提交记录,移到action_publish
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
|
||||
target_a = self.env["course.score.item"].search([
|
||||
("name", "=", "作业"),
|
||||
("create_uid", "=", self.env.uid)
|
||||
], limit=1)
|
||||
|
||||
if target_a:
|
||||
# 将刚新建的B追加到A的多对多字段
|
||||
# 将新建的作业追加到"作业"成绩项目的多对多字段
|
||||
target_a.write({
|
||||
"homework_ids": [(4, homework_record.id)]
|
||||
})
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
return homework_record
|
||||
@@ -93,13 +93,6 @@ class CourseHomeworkSubmit(models.Model):
|
||||
def create(self, vals):
|
||||
submit_rec = super().create(vals)
|
||||
self._check_submit_change_state(submit_rec)
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
=======
|
||||
>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb
|
||||
return submit_rec
|
||||
|
||||
def write(self, vals):
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# recents_limit —— 纯前端补丁模块,无 Python 模型
|
||||
# 仅通过 JS patch 把导航栏「最近」分组上限从 50 改为 20。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
'name': 'Recents Limit (20)',
|
||||
'version': '18.0.1.0.0',
|
||||
'summary': '把导航栏「最近」分组上限从 50 改为 20(继承 udoo_om_ux,不改动线上源码)',
|
||||
'description': """
|
||||
通过前端 patch 继承 udoo_om_ux 的 OmuxNavBar,把「最近访问」的分组上限
|
||||
从默认的 50 截到 20。
|
||||
|
||||
实现方式(不改动 udoo_om_ux 与 yuthon_base 的任何源码):
|
||||
1. setup 时把历史 localStorage 中可能 >20 的 recents 截断到 20;
|
||||
2. 监听 UDOO:FRC 事件,在每次新增「最近」后把上限压回 20。
|
||||
由于整个会话期间 this.uo.recents 恒 <= 20,beforeunload 持久化到
|
||||
localStorage 时自然也 <= 20,无需再 patch beforeunload 逻辑。
|
||||
""",
|
||||
'category': 'Tools',
|
||||
'depends': ['udoo_om_ux', 'web'],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'recents_limit/static/src/js/recents_limit.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
'author': 'Yuthon',
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/** @odoo-module **/
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { OmuxNavBar } from "@udoo_om_ux/webclient/navbar/navbar";
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
|
||||
// 「最近访问」分组的目标上限(线上 udoo_om_ux 默认为 50)
|
||||
const MAX_RECENTS = 20;
|
||||
|
||||
patch(OmuxNavBar.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
// 1) 处理从历史 localStorage 加载进来的 >20 条「最近」
|
||||
if (this.uo?.recents?.length > MAX_RECENTS) {
|
||||
this.uo.recents.length = MAX_RECENTS;
|
||||
}
|
||||
|
||||
// 2) 实时:每次新增「最近」访问后,把上限压回 20。
|
||||
// 原 OmuxNavBar 在 UDOO:FRC 事件里会把上限维持在 50,
|
||||
// 这里在其之后(先注册者先触发)再截到 20,
|
||||
// 于是整个会话期间 this.uo.recents 恒 <= 20,
|
||||
// 连带 beforeunload 持久化到 localStorage 也自然 <= 20。
|
||||
useBus(this.env.bus, "UDOO:FRC", () => {
|
||||
if (this.uo?.recents?.length > MAX_RECENTS) {
|
||||
this.uo.recents.length = MAX_RECENTS;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
'name': 'student_organization',
|
||||
'name': '学生组织',
|
||||
'version': '1.3',
|
||||
'summary': '',
|
||||
'summary': '管理学生组织架构、班级、年级等基础信息',
|
||||
'sequence': 11,
|
||||
'depends': ['base','hr'],
|
||||
'data':[
|
||||
@@ -14,6 +14,5 @@
|
||||
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Reference in New Issue
Block a user