30 Commits
Author SHA1 Message Date
qitangbo e5596aa299 Merge pull request '完善教育' (#61) from qtb into master
Reviewed-on: #61
2026-10-13 18:03:07 +08:00
qitangbo 1728ac79b7 完善教育 2026-10-13 17:54:16 +08:00
qitangbo 2025b4e344 Merge pull request 'Qtb' (#60) from qtb into master
Reviewed-on: #60
2026-10-12 19:26:46 +08:00
qitangbo fd091b566b 完善教育 2026-10-12 19:21:57 +08:00
qitangbo fea48a2623 fix(college): 关于页编辑按钮对所有用户隐藏(MutationObserver 兜底监听) 2026-10-12 16:53:12 +08:00
李鹏宇 6eb765546d Merge branch 'master' of https://git.pengyuthon.cn/pengyuthon/shcool
# Conflicts:
#	college/__manifest__.py
2026-10-10 16:36:20 +08:00
李鹏宇 cc92428709 列表视图样式 2026-10-10 16:35:35 +08:00
qitangbo e4f44b1287 Merge pull request '完善教育' (#59) from qtb into master
Reviewed-on: #59
2026-10-10 15:07:17 +08:00
qitangbo f1c9059887 完善教育 2026-10-10 11:43:01 +08:00
qitangbo d17e342196 Merge pull request '完善教育' (#58) from qtb into master
Reviewed-on: #58
2026-10-09 21:22:41 +08:00
qitangbo a3fc5395ea 完善教育 2026-10-09 21:22:20 +08:00
qitangbo 260a7347fb Merge pull request '完善教育' (#57) from qtb into master
Reviewed-on: #57
2026-10-09 19:19:26 +08:00
qitangbo 802b8efa39 完善教育 2026-10-09 19:19:05 +08:00
qitangbo ec4c918899 Merge pull request '关于页' (#56) from qtb into master
Reviewed-on: #56
2026-10-09 15:03:20 +08:00
qitangbo 87e8f0c636 关于页 2026-10-09 15:02:41 +08:00
qitangbo 8ea8b089af Merge pull request '完善教育' (#55) from qtb into master
Reviewed-on: #55
2026-10-09 14:23:19 +08:00
qitangbo dfa665bbde 编辑 2026-10-09 14:14:14 +08:00
qitangbo 8939e61094 feat: 新增 recents_limit 模块,继承 patch OmuxNavBar 将导航栏「最近」上限从50改为20(不改动线上源码/yuthon_base) 2026-10-08 21:08:31 +08:00
李鹏宇 0a96d0e868 用户菜单调整 2026-10-08 20:30:38 +08:00
李鹏宇 ab62d0e79b 用户菜单调整 2026-10-08 20:18:01 +08:00
李鹏宇 d84c482c84 用户菜单调整 2026-10-08 19:31:22 +08:00
李鹏宇 7b393414b3 用户菜单调整 2026-10-08 18:55:12 +08:00
李鹏宇 fa5b2c649e 用户菜单调整 2026-10-08 18:07:03 +08:00
李鹏宇 27b17aeaa6 用户菜单调整 2026-10-08 17:35:32 +08:00
qitangbo 246326ed9c Merge pull request '完善教育' (#54) from qtb into master
Reviewed-on: #54
2026-09-29 17:29:48 +08:00
qitangbo c84da2ed10 完善教育 2026-09-29 16:39:32 +08:00
qitangbo 9d5a60af09 Merge pull request '完善教育' (#53) from qtb into master
Reviewed-on: #53
2026-09-23 15:33:46 +08:00
qitangbo d1a217f8cf Merge pull request '当前用户' (#52) from qtb into master
Reviewed-on: #52
2026-09-22 16:40:27 +08:00
qitangbo 4cdbaf9799 Merge pull request '翻译' (#51) from qtb into master
Reviewed-on: #51
2026-09-21 19:41:06 +08:00
qitangbo 63c87c7e56 Merge pull request 'Qtb' (#50) from qtb into master
Reviewed-on: #50
2026-09-21 18:56:34 +08:00
25 changed files with 524 additions and 14 deletions
+6 -1
View File
@@ -1,6 +1,6 @@
{
'name': '教育',
'version': '18.0.3.0.4',
'version': '18.0.3.2.0',
'summary': 'Odoo 18 教学管理平台 — 师生管理 + 课程管理 + 作业/进展(老师·学生双角色)',
'description': """
Odoo 18 教学管理平台(面向老师与学生,双角色权限)
@@ -58,15 +58,20 @@
'assets': {
'web.assets_backend': [
'college/static/src/scss/college_theme.scss',
'college/static/src/scss/list_zebra.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',
'college/static/src/scss/list_zebra_dark.scss',
],
'web.assets_backend_lazy_dark': [
'college/static/src/scss/college_theme_dark.scss',
'college/static/src/scss/list_zebra_dark.scss',
],
},
'application': True,
+60
View File
@@ -236,4 +236,64 @@
</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>
+3 -1
View File
@@ -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>
+1
View File
@@ -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)
+16
View File
@@ -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')
+14
View File
@@ -106,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:
+10 -3
View File
@@ -107,11 +107,18 @@ class ExamQuestion(models.Model):
@api.onchange('question_type')
def _onchange_question_type(self):
"""切换题型时:判断题给默认正确答案;单选/多选在无选项时补 A/B/C/D。"""
"""切换题型时:判断题给默认正确答案;单选/多选在无选项时补 A/B/C/D;
非选择题清掉默认带出的空选项,否则会被一并提交,触发
ExamQuestionOption.content 的 required 校验。"""
if self.question_type == 'true_false':
self.correct_answer = 'True'
if self.question_type in ('single_choice', 'multiple_choice') and not self.option_ids:
self.option_ids = self._default_option_lines()
if self.question_type in ('single_choice', 'multiple_choice'):
if not self.option_ids:
self.option_ids = self._default_option_lines()
else:
# 切到填空/判断/简答:清掉默认带出的空选项,避免服务端必填校验报错
# 用 (5,0,0) 完整写法,(5,) 简写在 Odoo 18 + Py3.13 下会触发访问冲突
self.option_ids = [(5, 0, 0)]
@api.depends('correct_answer')
def _compute_correct_answer_tf(self):
+15
View File
@@ -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

+68
View File
@@ -0,0 +1,68 @@
/**
* 隐藏「关于页」(yuthon.about.page) 表单右上角的「编辑」按钮。
*
* 原理:form 视图已写 edit="false",Odoo 18 控制面板本就不该渲染编辑按钮;
* 但为保险(防止某些角色/动作仍带出编辑入口),这里在 FormController 上 patch:
* 当模型为 yuthon.about.page 时,用 MutationObserver 持续监听控制面板,
* 一旦编辑按钮(无论何时、以何种形式出现)被渲染,立刻隐藏。
*
* 注意:本文件改动需要「升级模块」才会生效(-d <库名> -u college),
* 并强刷浏览器 (Ctrl+Shift+R)。仅 git pull + 重启不会重建前端资源包。
*/
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { FormController } from "@web/views/form/form_controller";
import { onMounted, onWillUnmount } from "@odoo/owl";
const ABOUT_MODEL = "yuthon.about.page";
// 判定一个按钮是否为「编辑」按钮(兼容不同渲染形态)
function isEditButton(btn) {
if (!btn || btn.tagName !== "BUTTON") {
return false;
}
if (btn.classList.contains("o_form_button_edit")) {
return true;
}
if (btn.getAttribute("data-hotkey") === "e") {
return true;
}
const text = (btn.textContent || "").trim();
if (text === "编辑" || text.toLowerCase() === "edit") {
return true;
}
return false;
}
// 在控制面板内隐藏所有编辑按钮
function hideAboutEditButtons(root) {
(root || document).querySelectorAll(".o_control_panel button").forEach((btn) => {
if (isEditButton(btn)) {
btn.style.display = "none";
}
});
}
patch(FormController.prototype, {
setup() {
super.setup();
if (this.props.resModel !== ABOUT_MODEL) {
return;
}
let observer = null;
onMounted(() => {
hideAboutEditButtons(document);
// 控制面板按钮可能异步/重渲染后才出现,持续监听最稳妥
const cp = document.querySelector(".o_control_panel");
const target = cp || document.body;
observer = new MutationObserver(() => hideAboutEditButtons(target));
observer.observe(target, { childList: true, subtree: true });
});
onWillUnmount(() => {
if (observer) {
observer.disconnect();
}
});
},
});
@@ -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,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,7 @@
// 关于页(yuthon.about.page)为只读展示页:
// form 视图已设 edit="false",但教师组有该模型写权限时控制面板仍会渲染「编辑」按钮。
// 这里用 CSS 做兜底隐藏(若按钮恰好在 form 内),控制面板中的按钮由同名的 JS patch 处理。
.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;
}
+99
View File
@@ -0,0 +1,99 @@
// ============================================================
// 列表视图:单元格网格线 + 斑马纹 + 悬停高亮(全局生效,纯 CSS)
// 由 Odoo 17 的实现改写并适配 Odoo 18。写进 college 模块,
// 不改 odoo18 源码、不给任何视图加 class、不碰业务代码。
//
// 注意:这里用 SCSS 嵌套来表达长选择器链,**不要**把选择器字符串存进
// SCSS 变量(`$x: body.o_web_client ...`)—— libsass 会把 `body.o_web_client`
// 当成表达式解析报 "Invalid CSS after ...: expected expression"。
//
// 遵守的约束(对应参考提示里的 6 个坑):
// 1. 纯 CSS 挂 web.assets_backend,不继承 QWeb 模板(web.ListView 在 18 里同样没注册成
// ir.model.data,用 inherit_id 会报 External ID not found)
// 2. 不依赖后端 _get_view 注入 class;18 的列表 view_type / arch 根标签同样是 list
// 3. td 只写 border / padding / 背景,绝不写 color、font-weight —— Odoo 原生
// decoration-*(text-success / text-muted / fw-bold 加在 tr 上、靠继承生效)不被盖掉
// 4. 斑马纹 / 悬停写在 td 上(tr 的背景常被主题覆盖),并排除选中行与装饰行
// 5. 选择器长链 + 关键属性 !important,压过主题样式
// 6. 改完要 `-u college` 重建资源包,再 Ctrl+F5 强刷
//
// Odoo 18 与 17 的差异(下面已逐条处理):
// · 未分组列表带 .table-striped,Bootstrap 5.3 用 --bs-table-bg-type 给单元格上色
// → 我们的背景直接写在 td 上并加 !important,稳定压过原生条纹(原生顺序同样是 even)
// · 分组列表的「分组头」行是 tbody > tr > th(不是 td),所以边框规则额外覆盖 tbody th
// · 选中行除 .table-info 外还会带 .o_data_row_selected → 一并排除
// · 可编辑列表(editable="top|bottom")的必填 / 校验失败单元格靠 border-bottom 表达,
// 这里显式还原,避免被网格线的 !important 盖掉(业务提示不能丢)
// · 表格是 border-collapse: collapse,相邻边框会自动合并,无需去重加粗
// ============================================================
$lz-border: #b6c0cc;
$lz-stripe: #F1F6FD; // 偶数数据行
$lz-hover: #cfe2fb; // 悬停数据行
body.o_web_client .o_action_manager .o_list_view {
// 表格本体:不要外框,白底(奇数行的“白”就来自这里)
.o_list_table {
border-collapse: collapse !important;
border: none !important;
background-color: #ffffff !important;
// 表头单元格:加粗 + 统一边框 / 内边距 / 左对齐;底色沿用主题,不写死
thead th {
font-weight: 700 !important;
border: 1px solid $lz-border !important;
padding: 11px 14px !important;
text-align: left !important;
}
// 数据单元格:只声明边框与内边距(颜色、字重交给 tr 继承)
tbody td {
border: 1px solid $lz-border !important;
padding: 10px 14px !important;
}
// 分组头单元格(Odoo 18 里是 tbody th):给网格线,但不动它的字色 / 字重
tbody th {
border: 1px solid $lz-border !important;
padding: 10px 14px !important;
}
// 去掉最外层四边(上 / 下 / 左 / 右),只保留单元格之间的内部网格
thead tr:first-child > * {
border-top: none !important;
}
tbody tr:last-child > * {
border-bottom: none !important;
}
thead tr > *:first-child,
tbody tr > *:first-child {
border-left: none !important;
}
thead tr > *:last-child,
tbody tr > *:last-child {
border-right: none !important;
}
// 可编辑列表:还原 Odoo 的「必填 / 校验失败」提示线(它们用 border-bottom 表达)
tbody tr.o_selected_row > td.o_required_modifier:not(.o_readonly_modifier) {
border-bottom-color: $o-input-border-required !important;
}
tbody tr.o_selected_row > td.o_invalid_cell:not(.o_readonly_modifier) {
border-bottom-color: $danger !important;
}
// 斑马纹:偶数数据行淡蓝,奇数行保持白色(排除选中行、装饰行)
tbody tr.o_data_row:nth-child(even):not(.table-info):not(.o_data_row_selected):not([class*="text-"]) > td {
background-color: $lz-stripe !important;
}
// 悬停高亮:排除选中行,同样写在 td 上
tbody tr.o_data_row:not(.table-info):not(.o_data_row_selected):hover > td {
background-color: $lz-hover !important;
}
}
.o_list_table * {
box-sizing: border-box !important;
}
}
@@ -0,0 +1,40 @@
// ============================================================
// 列表视图网格 / 斑马纹 —— 深色模式补充
// 只注册在 web.assets_web_dark 与 web.assets_backend_lazy_dark(深色主题才加载),
// 浅色模式完全不受影响;颜色按本项目深色约定硬编码
// (页底 #111827、卡片面 #1e293b、描边 #334155),不依赖主题 SCSS 变量。
//
// 选择器比 list_zebra.scss 多一层 .o_list_renderer,特异性更高
// → 无论两个 bundle 的先后顺序如何,都能稳定覆盖浅色值。
// (同样用嵌套写法,不把选择器存进变量。)
// ============================================================
$lz-d-border: #334155;
$lz-d-surface: #1e293b; // 表格底色
$lz-d-stripe: #26344a; // 偶数行
$lz-d-hover: #2f4d78; // 悬停
body.o_web_client .o_action_manager .o_list_view .o_list_renderer {
// 表格本体:深色面,不加外框
.o_list_table {
background-color: $lz-d-surface !important;
border: none !important;
// 网格线换成深色描边(表头 / 数据 / 分组头)
thead th,
tbody td,
tbody th {
border-color: $lz-d-border !important;
}
// 斑马纹(排除选中行、装饰行,与浅色版一致)
tbody tr.o_data_row:nth-child(even):not(.table-info):not(.o_data_row_selected):not([class*="text-"]) > td {
background-color: $lz-d-stripe !important;
}
// 悬停高亮
tbody tr.o_data_row:not(.table-info):not(.o_data_row_selected):hover > td {
background-color: $lz-d-hover !important;
}
}
}
+2 -1
View File
@@ -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>
+19 -2
View File
@@ -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/>
+8 -2
View File
@@ -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"/>
+14
View File
@@ -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>
+7 -3
View File
@@ -27,8 +27,8 @@ class LearningCourse(models.Model):
syllabus_ids = fields.One2many('course.syllabus', 'course_id', string='教学大纲/教案')
syllabus_count = fields.Integer(string='大纲/教案数', compute='_compute_syllabus_count', store=True)
# 按类型统计
syllabus_count_total = fields.Integer(string='大纲总数', compute='_compute_syllabus_count')
lesson_plan_count = fields.Integer(string='教案总数', compute='_compute_syllabus_count')
syllabus_count_total = fields.Integer(string='大纲总数', compute='_compute_syllabus_type_count')
lesson_plan_count = fields.Integer(string='教案总数', compute='_compute_syllabus_type_count')
# ==================== 统计资源(通过章节关联) ====================
resource_count = fields.Integer(string='资源总数', compute='_compute_resource_count', store=True)
video_count = fields.Integer(string='视频数', compute='_compute_resource_count', store=True)
@@ -56,10 +56,14 @@ class LearningCourse(models.Model):
for record in self:
record.chapter_count = len(record.chapter_ids)
@api.depends('syllabus_ids', 'syllabus_ids.syllabus_type')
@api.depends('syllabus_ids')
def _compute_syllabus_count(self):
for record in self:
record.syllabus_count = len(record.syllabus_ids)
@api.depends('syllabus_ids', 'syllabus_ids.syllabus_type')
def _compute_syllabus_type_count(self):
for record in self:
record.syllabus_count_total = len(record.syllabus_ids.filtered(lambda s: s.syllabus_type == 'syllabus'))
record.lesson_plan_count = len(record.syllabus_ids.filtered(lambda s: s.syllabus_type == 'lesson_plan'))
+2
View File
@@ -0,0 +1,2 @@
# recents_limit —— 纯前端补丁模块,无 Python 模型
# 仅通过 JS patch 把导航栏「最近」分组上限从 50 改为 20。
+26
View File
@@ -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;
}
});
},
});
@@ -27,7 +27,9 @@ class StudentOrganization(models.Model):
parent_path = fields.Char(index=True)
# 完整名称(显示层级)
full_name = fields.Char(string='完整名称', compute='_compute_full_name', store=True)
# recursive=True: full_name 依赖 parent_id.full_name(自引用层级),
# 必须声明,否则 Odoo 18 报 UserWarning 且父级改名时子级不会重算
full_name = fields.Char(string='完整名称', compute='_compute_full_name', store=True, recursive=True)
# 负责人信息(学院用)
manager_id = fields.Many2one('res.users', string='负责人')
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB