zhou #21

Merged
Ghost merged 2 commits from zhou into master 2026-08-07 18:46:05 +08:00
11 changed files with 697 additions and 57 deletions
+11
View File
@@ -45,6 +45,17 @@
'views/about_page_views.xml',
'views/menus.xml',
],
'assets': {
'web.assets_backend': [
'college/static/src/scss/college_theme.scss',
],
'web.assets_web_dark': [
'college/static/src/scss/college_theme_dark.scss',
],
'web.assets_backend_lazy_dark': [
'college/static/src/scss/college_theme_dark.scss',
],
},
'application': True,
'installable': True,
'license': 'LGPL-3',
+199
View File
@@ -1,3 +1,5 @@
import re
from odoo import api, fields, models
@@ -53,6 +55,191 @@ class CourseChapterAttachment(models.Model):
return super().write(vals)
class CourseChapterExercise(models.Model):
"""章节训练习题——老师按章节布置的随堂简答题。
一个章节可挂多道;学生可以自由作答(不计分、不强制),
学员的回答存于 `yuthon.school.course.chapter.exercise.answer`。
"""
_name = 'yuthon.school.course.chapter.exercise'
_description = '章节训练习题'
_order = 'chapter_id, sequence, id'
_rec_name = 'exercise_label'
chapter_id = fields.Many2one(
'yuthon.school.course.chapter', string='章节',
required=True, ondelete='cascade', index=True)
sequence = fields.Integer(string='排序', default=10)
question = fields.Html(string='题目', sanitize=True, required=True)
analysis = fields.Html(
string='参考解析', sanitize=True,
help='给学员的参考答案/思路(仅展示,不参与评分)')
exercise_label = fields.Char(
string='显示名', compute='_compute_exercise_label',
help='内嵌列表与表单标题用:第 N 题')
answer_ids = fields.One2many(
'yuthon.school.course.chapter.exercise.answer', 'exercise_id',
string='学员作答')
answer_count = fields.Integer(
string='作答人数', compute='_compute_answer_count', store=True)
course_id = fields.Many2one(
'yuthon.school.course', string='课程',
related='chapter_id.course_id', store=True, index=True)
# 当前学员的回答(depends_context('uid') 跟随当前登录用户切换)
my_answer_content = fields.Html(
string='我的回答(全文)',
compute='_compute_my_answer', depends_context='uid')
my_answer_preview = fields.Char(
string='我的回答',
compute='_compute_my_answer_preview', depends_context='uid',
help='去除 Html 标签后的纯文本预览,便于在列表里紧凑显示')
@api.depends('sequence')
def _compute_exercise_label(self):
for rec in self:
rec.exercise_label = f'第 {rec.sequence} 题'
@api.depends('answer_ids')
def _compute_answer_count(self):
for rec in self:
rec.answer_count = len(rec.answer_ids)
@api.depends_context('uid')
def _compute_my_answer(self):
"""当前登录学员对本习题的回答(未作答则为 False)。"""
Answer = self.env['yuthon.school.course.chapter.exercise.answer']
student = self.env['yuthon.school.student'].sudo().search(
[('user_id', '=', self.env.uid)], limit=1)
answers = {}
if student:
for a in Answer.search([
('student_id', '=', student.id),
('exercise_id', 'in', self.ids),
]):
answers[a.exercise_id.id] = a
for rec in self:
ans = answers.get(rec.id)
rec.my_answer_content = ans.content if ans else False
@api.depends_context('uid')
def _compute_my_answer_preview(self):
"""去除 Html 标签后的纯文本预览(80 字以内)。"""
for rec in self:
content = rec.my_answer_content or ''
text = re.sub(r'<[^>]+>', ' ', content)
text = re.sub(r'\s+', ' ', text).strip()
if not text:
rec.my_answer_preview = ''
elif len(text) > 80:
rec.my_answer_preview = text[:80] + '…'
else:
rec.my_answer_preview = text
def action_answer(self):
"""学生端:弹窗回答本题。
若当前用户已对本题作答过,则打开已有作答;否则新建一条空作答。
"""
self.ensure_one()
Answer = self.env['yuthon.school.course.chapter.exercise.answer']
student = self.env['yuthon.school.student'].sudo().search(
[('user_id', '=', self.env.uid)], limit=1)
if not student:
return {'type': 'ir.actions.act_window_close'}
answer = Answer.search([
('exercise_id', '=', self.id),
('student_id', '=', student.id),
], limit=1)
if not answer:
answer = Answer.create({
'exercise_id': self.id,
'student_id': student.id,
})
return {
'type': 'ir.actions.act_window',
'name': '作答',
'res_model': 'yuthon.school.course.chapter.exercise.answer',
'res_id': answer.id,
'view_mode': 'form',
'view_id': self.env.ref(
'college.yuthon_school_course_chapter_exercise_answer_form').id,
'views': [(self.env.ref(
'college.yuthon_school_course_chapter_exercise_answer_form').id,
'form')],
'target': 'new',
}
def action_view_answers(self):
"""老师端:弹窗查看本题所有学员作答。
弹出只读列表(学员 / 回答 / 作答时间),不可编辑(不参与评分)。
"""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': f'学员作答 · {self.exercise_label}',
'res_model': 'yuthon.school.course.chapter.exercise.answer',
'view_mode': 'list,form',
'domain': [('exercise_id', '=', self.id)],
'views': [
(self.env.ref(
'college.yuthon_school_course_chapter_exercise_answer_view_tree_teacher').id,
'list'),
(False, 'form'),
],
'target': 'new',
'context': {'default_exercise_id': self.id},
}
class CourseChapterExerciseAnswer(models.Model):
"""学员对章节训练习题的回答(不计分、自由提交、可修改)。"""
_name = 'yuthon.school.course.chapter.exercise.answer'
_description = '习题作答'
_order = 'exercise_id, student_id, id'
_rec_name = 'exercise_id'
exercise_id = fields.Many2one(
'yuthon.school.course.chapter.exercise', string='习题',
required=True, ondelete='cascade', index=True)
student_id = fields.Many2one(
'yuthon.school.student', string='学员',
required=True, ondelete='cascade', index=True)
user_id = fields.Many2one(
'res.users', string='账号',
related='student_id.user_id', store=True, index=True)
content = fields.Html(
string='我的回答', sanitize=True,
help='学员的作答内容,可随时修改(不计分)')
submit_time = fields.Datetime(
string='最后作答时间', default=fields.Datetime.now)
chapter_id = fields.Many2one(
'yuthon.school.course.chapter', string='章节',
related='exercise_id.chapter_id', store=True, index=True)
course_id = fields.Many2one(
'yuthon.school.course', string='课程',
related='exercise_id.course_id', store=True, index=True)
question_display = fields.Html(
string='题目', related='exercise_id.question', readonly=True)
@api.model_create_multi
def create(self, vals_list):
vals_list = [dict(v, submit_time=fields.Datetime.now())
for v in vals_list]
return super().create(vals_list)
def write(self, vals):
if 'content' in vals:
vals['submit_time'] = fields.Datetime.now()
return super().write(vals)
class CourseChapter(models.Model):
"""课程章节——老师端在课程内容里维护,学生端作为学习目录。
@@ -81,6 +268,18 @@ class CourseChapter(models.Model):
attachment_ids = fields.One2many(
'yuthon.school.course.chapter.attachment', 'chapter_id', string='附件')
# 训练习题:一个章节可挂多道简答题(学生可自由作答,不计分)
exercise_ids = fields.One2many(
'yuthon.school.course.chapter.exercise', 'chapter_id',
string='训练习题')
exercise_count = fields.Integer(
string='习题数', compute='_compute_exercise_count', store=True)
@api.depends('exercise_ids')
def _compute_exercise_count(self):
for chapter in self:
chapter.exercise_count = len(chapter.exercise_ids)
# 随堂笔记:老师按章节记录的富文本笔记(与章节编辑表单共用同一字段)
notes = fields.Html(string='随堂笔记', sanitize=True)
+12
View File
@@ -15,4 +15,16 @@
<field name="category_id" ref="module_category_education"/>
<field name="comment">可查看自己选课的课程、提交作业、查看自己的学习进展,不能创建课程。</field>
</record>
<!-- 学生只能访问自己的习题作答 -->
<record id="rule_exercise_answer_student_own" model="ir.rule">
<field name="name">习题作答:学生仅能访问自己的回答</field>
<field name="model_id" ref="model_yuthon_school_course_chapter_exercise_answer"/>
<field name="groups" eval="[(4, ref('college.group_student'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
</odoo>
+6
View File
@@ -30,6 +30,12 @@ access_yuthon_school_course_chapter_student,yuthon.school.course.chapter.student
access_yuthon_school_course_chapter_attachment_admin,yuthon.school.course.chapter.attachment.admin,model_yuthon_school_course_chapter_attachment,base.group_system,1,1,1,1
access_yuthon_school_course_chapter_attachment_teacher,yuthon.school.course.chapter.attachment.teacher,model_yuthon_school_course_chapter_attachment,college.group_teacher,1,1,1,1
access_yuthon_school_course_chapter_attachment_student,yuthon.school.course.chapter.attachment.student,model_yuthon_school_course_chapter_attachment,college.group_student,1,0,0,0
access_yuthon_school_course_chapter_exercise_admin,yuthon.school.course.chapter.exercise.admin,model_yuthon_school_course_chapter_exercise,base.group_system,1,1,1,1
access_yuthon_school_course_chapter_exercise_teacher,yuthon.school.course.chapter.exercise.teacher,model_yuthon_school_course_chapter_exercise,college.group_teacher,1,1,1,1
access_yuthon_school_course_chapter_exercise_student,yuthon.school.course.chapter.exercise.student,model_yuthon_school_course_chapter_exercise,college.group_student,1,0,0,0
access_yuthon_school_course_chapter_exercise_answer_admin,yuthon.school.course.chapter.exercise.answer.admin,model_yuthon_school_course_chapter_exercise_answer,base.group_system,1,1,1,1
access_yuthon_school_course_chapter_exercise_answer_teacher,yuthon.school.course.chapter.exercise.answer.teacher,model_yuthon_school_course_chapter_exercise_answer,college.group_teacher,1,0,0,0
access_yuthon_school_course_chapter_exercise_answer_student,yuthon.school.course.chapter.exercise.answer.student,model_yuthon_school_course_chapter_exercise_answer,college.group_student,1,1,1,0
access_yuthon_school_training_admin,yuthon.school.training.admin,model_yuthon_school_training,base.group_system,1,1,1,1
access_yuthon_school_training_teacher,yuthon.school.training.teacher,model_yuthon_school_training,college.group_teacher,1,1,1,1
access_yuthon_school_training_student,yuthon.school.training.student,model_yuthon_school_training,college.group_student,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
30 access_yuthon_school_course_chapter_attachment_admin yuthon.school.course.chapter.attachment.admin model_yuthon_school_course_chapter_attachment base.group_system 1 1 1 1
31 access_yuthon_school_course_chapter_attachment_teacher yuthon.school.course.chapter.attachment.teacher model_yuthon_school_course_chapter_attachment college.group_teacher 1 1 1 1
32 access_yuthon_school_course_chapter_attachment_student yuthon.school.course.chapter.attachment.student model_yuthon_school_course_chapter_attachment college.group_student 1 0 0 0
33 access_yuthon_school_course_chapter_exercise_admin yuthon.school.course.chapter.exercise.admin model_yuthon_school_course_chapter_exercise base.group_system 1 1 1 1
34 access_yuthon_school_course_chapter_exercise_teacher yuthon.school.course.chapter.exercise.teacher model_yuthon_school_course_chapter_exercise college.group_teacher 1 1 1 1
35 access_yuthon_school_course_chapter_exercise_student yuthon.school.course.chapter.exercise.student model_yuthon_school_course_chapter_exercise college.group_student 1 0 0 0
36 access_yuthon_school_course_chapter_exercise_answer_admin yuthon.school.course.chapter.exercise.answer.admin model_yuthon_school_course_chapter_exercise_answer base.group_system 1 1 1 1
37 access_yuthon_school_course_chapter_exercise_answer_teacher yuthon.school.course.chapter.exercise.answer.teacher model_yuthon_school_course_chapter_exercise_answer college.group_teacher 1 0 0 0
38 access_yuthon_school_course_chapter_exercise_answer_student yuthon.school.course.chapter.exercise.answer.student model_yuthon_school_course_chapter_exercise_answer college.group_student 1 1 1 0
39 access_yuthon_school_training_admin yuthon.school.training.admin model_yuthon_school_training base.group_system 1 1 1 1
40 access_yuthon_school_training_teacher yuthon.school.training.teacher model_yuthon_school_training college.group_teacher 1 1 1 1
41 access_yuthon_school_training_student yuthon.school.training.student model_yuthon_school_training college.group_student 1 1 1 1
+149
View File
@@ -0,0 +1,149 @@
// ============================================================
// college 看板主题适配
// 使用 Odoo SCSS 变量,自动跟随 udoo_om_ux 等主题的浅色/深色色板。
// 需同时注册到 web.assets_backend(浅色)与 web.assets_web_dark
// / web.assets_backend_lazy_dark(深色)。
// ============================================================
.college-course-kanban {
.college-kanban-card {
background-color: $o-view-background-color;
color: $o-main-text-color;
border: 1px solid rgba($o-main-text-color, 0.08);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
border-radius: 8px;
overflow: hidden;
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.college-kanban-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 6px;
color: $o-main-text-color;
}
.college-kanban-meta {
font-size: 13px;
color: $o-gray-600;
&.text-muted,
.text-muted {
color: $o-gray-600 !important;
}
.fa {
color: $o-gray-500;
}
}
.college-kanban-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
}
// ---------------- 老师端:蓝调专业风 ----------------
&.college-kanban-teacher .college-kanban-card {
.college-kanban-badge-hours {
background: rgba($o-info, 0.15);
color: $o-main-text-color;
}
.college-kanban-badge-students {
background: rgba($o-success, 0.15);
color: $o-main-text-color;
}
}
// ---------------- 学生-我的课程:书本青蓝风 ----------------
&.college-kanban-my .college-kanban-card {
.college-kanban-badge-type {
background: rgba($o-info, 0.85);
color: #ffffff;
}
.college-kanban-badge-public {
background: rgba($o-success, 0.85);
color: #ffffff;
}
}
// ---------------- 学生-课程学习:活力橙风 ----------------
&.college-kanban-study .college-kanban-card {
.college-kanban-badge-type {
background: rgba($o-view-background-color, 0.92);
color: $o-danger;
}
.college-kanban-badge-hours {
background: rgba($o-warning, 0.15);
color: $o-main-text-color;
}
.college-kanban-badge-chapters {
background: rgba($o-info, 0.15);
color: $o-main-text-color;
}
}
// ---------------- 学习进展:深森林绿 ----------------
&.college-kanban-progress .college-kanban-card {
.college-kanban-title {
color: #ffffff;
}
.college-kanban-badge-score {
background: rgba($o-view-background-color, 0.92);
color: $o-success;
}
.college-kanban-progress-rate {
color: $o-main-text-color;
font-weight: 600;
}
}
// ---------------- 我的实训:紫罗兰 ----------------
&.college-kanban-training .college-kanban-card {
.college-kanban-title {
color: #ffffff;
}
.college-kanban-badge-purple {
background: rgba($o-info, 0.15);
color: $o-main-text-color;
}
.college-kanban-badge-pink {
background: rgba($o-danger, 0.15);
color: $o-main-text-color;
}
}
// ---------------- 作业提交:珊瑚红 ----------------
&.college-kanban-homework .college-kanban-card {
.college-kanban-title {
color: #ffffff;
}
.college-kanban-badge-score {
background: rgba($o-view-background-color, 0.9);
color: $o-danger;
}
.college-kanban-badge-state {
background: rgba($o-danger, 0.15);
color: $o-main-text-color;
}
}
}
@@ -0,0 +1,191 @@
// ============================================================
// college 看板「深色模式」专属样式
// 本文件只注册在 web.assets_web_dark / web.assets_backend_lazy_dark,
// 这两个 bundle 仅当用户开启深色主题时才会加载,因此下面的颜色
// 全部硬编码,不依赖任何主题 SCSS 变量(避免跨模块编译顺序导致
// 变量未切换成深色值的问题)。
// 深色页面底色约 #111827,卡片用略亮的 slate-800 (#1e293b) 形成层次。
// ============================================================
// 顶部同级菜单段(如「我的课程 / 全部课程」)夜间白字加粗
.nav-segment .nav-link {
color: #f1f5f9 !important;
font-weight: 600 !important;
}
.nav-segment .nav-link.active {
color: #ffffff !important;
font-weight: 700 !important;
}
.college-course-kanban {
.college-kanban-card {
background-color: #1e293b;
color: #f1f5f9;
border: 1px solid #334155;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
border-radius: 8px;
overflow: hidden;
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
}
.college-kanban-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 6px;
color: #f8fafc !important;
}
.college-kanban-body {
background-color: #1e293b;
}
.college-kanban-meta {
font-size: 13px;
color: #94a3b8 !important;
&.text-muted,
.text-muted {
color: #94a3b8 !important;
}
.fa {
color: #64748b !important;
}
}
.college-kanban-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
}
// ---------------- 老师端:蓝调专业风 ----------------
&.college-kanban-teacher .college-kanban-card {
// 头部渐变稍压暗,避免与深色页面「糊」在一起,白字依旧清晰
> div:first-child {
background: linear-gradient(135deg, #2563eb, #4f46e5) !important;
}
.college-kanban-badge-hours {
background: rgba(96, 165, 250, 0.18);
color: #93c5fd !important;
}
.college-kanban-badge-students {
background: rgba(52, 211, 153, 0.18);
color: #6ee7b7 !important;
}
}
// ---------------- 学生-我的课程:书本青蓝风 ----------------
&.college-kanban-my .college-kanban-card {
> div:first-child {
background: linear-gradient(135deg, #0ea5e9, #06b6d4) !important;
}
.college-kanban-badge-type {
background: #38bdf8;
color: #0f172a !important;
}
.college-kanban-badge-public {
background: #34d399;
color: #0f172a !important;
}
}
// ---------------- 学生-课程学习:活力橙风 ----------------
&.college-kanban-study .college-kanban-card {
> div:first-child {
background: linear-gradient(120deg, #f97316, #ef4444) !important;
}
.college-kanban-badge-type {
background: rgba(251, 146, 60, 0.22);
color: #fdba74 !important;
}
.college-kanban-badge-hours {
background: rgba(251, 191, 36, 0.18);
color: #fcd34d !important;
}
.college-kanban-badge-chapters {
background: rgba(96, 165, 250, 0.18);
color: #93c5fd !important;
}
}
// ---------------- 学习进展:深森林绿 ----------------
&.college-kanban-progress .college-kanban-card {
> div:first-child {
background: linear-gradient(135deg, #2d6a4f, #40916c) !important;
}
.college-kanban-title {
color: #f8fafc !important;
}
.college-kanban-badge-score {
background: rgba(52, 211, 153, 0.2);
color: #6ee7b7 !important;
}
.college-kanban-progress-rate {
color: #f1f5f9 !important;
font-weight: 600;
}
}
// ---------------- 我的实训:紫罗兰 ----------------
&.college-kanban-training .college-kanban-card {
> div:first-child {
background: linear-gradient(135deg, #7c3aed, #c026d3) !important;
}
.college-kanban-title {
color: #f8fafc !important;
}
.college-kanban-badge-purple {
background: rgba(167, 139, 250, 0.2);
color: #c4b5fd !important;
}
.college-kanban-badge-pink {
background: rgba(244, 114, 182, 0.2);
color: #f9a8d4 !important;
}
}
// ---------------- 作业提交:珊瑚红 ----------------
&.college-kanban-homework .college-kanban-card {
> div:first-child {
background: linear-gradient(135deg, #e11d48, #f97316) !important;
}
.college-kanban-title {
color: #f8fafc !important;
}
.college-kanban-badge-score {
background: rgba(248, 113, 113, 0.2);
color: #fca5a5 !important;
}
.college-kanban-badge-state {
background: rgba(248, 113, 113, 0.18);
color: #fca5a5 !important;
}
}
}
+74
View File
@@ -16,6 +16,19 @@
<field name="description" widget="html" string="章节说明"/>
<field name="notes" widget="html" string="随堂笔记"/>
</group>
<separator string="训练习题(均为简答题,可添加多道;学生可自由作答,不计分)"/>
<field name="exercise_ids" string="训练习题">
<list editable="bottom" decoration-muted="sequence &lt; 0">
<field name="sequence" widget="handle" string="排序"/>
<field name="question" string="题目"/>
<field name="analysis" string="参考解析" optional="show"/>
<field name="answer_count" string="作答人数"/>
<button name="action_view_answers" type="object"
string="查看作答" class="btn-link"
icon="fa-comments"
title="查看本题所有学员的回答"/>
</list>
</field>
<group>
<field name="video_file" filename="video_filename"/>
<field name="video_filename" invisible="1"/>
@@ -69,11 +82,72 @@
nolabel="1"/>
</list>
</field>
<!-- 训练习题:可自由作答,不计分 -->
<separator string="训练习题(可自由作答,不计分)" invisible="not exercise_ids"/>
<field name="exercise_ids" readonly="1"
invisible="not exercise_ids">
<list editable="bottom" create="0" delete="0">
<field name="sequence" string="序号"/>
<field name="question" string="题目" readonly="1"/>
<field name="my_answer_preview" string="我的回答"
placeholder="(未作答)" optional="show"/>
<field name="my_answer_content" string="我的回答(详细)"
optional="hide"/>
<field name="answer_count" string="已作答人数" optional="show"/>
<button name="action_answer" type="object"
string="作答 / 修改" class="btn-primary"
icon="fa-pencil"/>
</list>
</field>
</sheet>
</form>
</field>
</record>
<!-- ===================== 学员作答:弹窗表单 ===================== -->
<record id="yuthon_school_course_chapter_exercise_answer_form" model="ir.ui.view">
<field name="name">yuthon.school.course.chapter.exercise.answer.form</field>
<field name="model">yuthon.school.course.chapter.exercise.answer</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="exercise_id" invisible="1"/>
<field name="student_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="submit_time" invisible="1"/>
</group>
<div class="text-muted small mb-1">
所属课程:<field name="course_id" readonly="1" nolabel="1" class="oe_inline"/>
· 所属章节:<field name="chapter_id" readonly="1" nolabel="1" class="oe_inline"/>
</div>
<separator string="题目"/>
<field name="question_display" widget="html" readonly="1" nolabel="1"/>
<separator string="我的回答(自由作答,不计分,可随时修改)"/>
<field name="content" widget="html" nolabel="1"
placeholder="请输入你的回答..."/>
<div class="text-muted small mt-2" invisible="not submit_time">
上次作答时间:<field name="submit_time" readonly="1" nolabel="1" class="oe_inline"/>
</div>
</sheet>
</form>
</field>
</record>
<!-- ===================== 学员作答:老师只读列表(弹窗查看用) ===================== -->
<record id="yuthon_school_course_chapter_exercise_answer_view_tree_teacher" model="ir.ui.view">
<field name="name">yuthon.school.course.chapter.exercise.answer.tree.teacher</field>
<field name="model">yuthon.school.course.chapter.exercise.answer</field>
<field name="groups_id" eval="[(4, ref('college.group_teacher')), (4, ref('base.group_system'))]"/>
<field name="arch" type="xml">
<list editable="bottom" create="0" delete="0">
<field name="student_id" string="学员" readonly="1"/>
<field name="content" string="回答内容" readonly="1"/>
<field name="submit_time" string="作答时间" readonly="1"/>
</list>
</field>
</record>
<!-- ===================== 课程学习:学生专用表单(仅学生可见,避免覆盖老师默认表单) ===================== -->
<record id="yuthon_school_course_study_form" model="ir.ui.view">
<field name="name">yuthon.school.course.study.form</field>
+28 -30
View File
@@ -203,7 +203,7 @@
<field name="arch" type="xml">
<kanban default_group_by="state"
default_order="semester,college,name"
class="o_kanban_mobile">
class="college-course-kanban college-kanban-teacher o_kanban_mobile">
<field name="name"/>
<field name="college"/>
<field name="semester"/>
@@ -214,8 +214,8 @@
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:8px; overflow:hidden; box-shadow:0 1px 4px rgba(59,130,246,.15); border:1px solid rgba(59,130,246,.1);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:8px; overflow:hidden;">
<!-- 顶帽:蓝调 + 课程编号 -->
<div style="background:linear-gradient(135deg,#3B82F6,#6366F1); padding:10px 14px; display:flex; align-items:center; justify-content:space-between;">
<div style="color:#fff; font-size:11px; opacity:.85; letter-spacing:.5px;">
@@ -227,21 +227,21 @@
</span>
</div>
<!-- 主体 -->
<div style="padding:10px 14px; background:#fff;">
<div style="font-size:14px; font-weight:600; color:#1E3A5F; margin-bottom:6px;">
<div class="college-kanban-body" style="padding:10px 14px;">
<div class="college-kanban-title">
<field name="name"/>
</div>
<div class="text-muted small mb-1">
<div class="college-kanban-meta text-muted small mb-1">
<i class="fa fa-building"/> <field name="college"/> · <field name="semester"/>
</div>
<div class="small mb-1">
<div class="college-kanban-meta small mb-1">
<i class="fa fa-user text-muted"/> <field name="teacher_id"/>
</div>
<div style="display:flex; gap:8px; margin-top:8px;">
<span style="background:#EFF6FF; color:#1D4ED8; padding:3px 10px; border-radius:12px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-hours">
<i class="fa fa-clock-o"/> <field name="hours"/> 课时
</span>
<span style="background:#F0FDF4; color:#15803D; padding:3px 10px; border-radius:12px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-students">
<i class="fa fa-users"/> <field name="student_count"/> 学员
</span>
</div>
@@ -262,7 +262,7 @@
<field name="arch" type="xml">
<kanban default_group_by="semester"
default_order="college,name"
class="o_kanban_mobile">
class="college-course-kanban college-kanban-my o_kanban_mobile">
<field name="name"/>
<field name="code"/>
<field name="college"/>
@@ -276,8 +276,8 @@
colors='{"done": "success", "warning": "warning"}'/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:8px; overflow:hidden; box-shadow:0 1px 3px rgba(0,0,0,.08);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:8px; overflow:hidden;">
<!-- 顶帽:彩色条 + 课程编号 -->
<div style="background:linear-gradient(135deg,#5B86E5,#36D1DC); padding:10px 14px;">
<div style="color:#fff; font-size:11px; opacity:.85; letter-spacing:.5px;">
@@ -288,24 +288,22 @@
</div>
</div>
<!-- 主体 -->
<div style="padding:10px 14px; background:#fff;">
<div class="college-kanban-body" style="padding:10px 14px;">
<div class="mb-2">
<span class="badge text-white"
style="background:#36D1DC; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-type">
<field name="course_type"/>
</span>
<t t-if="record.is_public.raw_value">
<span class="badge text-white ml-1"
style="background:#43e97b; font-size:11px;">公开</span>
<span class="college-kanban-badge college-kanban-badge-public ml-1">公开</span>
</t>
</div>
<div class="text-muted small mb-1">
<div class="college-kanban-meta text-muted small mb-1">
<field name="college"/>
</div>
<div class="small mb-1">
<div class="college-kanban-meta small mb-1">
<i class="fa fa-user-circle-o text-muted"/> <field name="teacher_id" widget="many2one_avatar_user"/>
</div>
<div class="small text-muted">
<div class="college-kanban-meta small text-muted">
<i class="fa fa-clock-o"/> <field name="hours"/> 课时 · 共 <field name="chapter_count"/> 章节
</div>
</div>
@@ -325,7 +323,7 @@
<field name="arch" type="xml">
<kanban default_group_by="college"
default_order="semester,name"
class="o_kanban_mobile">
class="college-course-kanban college-kanban-study o_kanban_mobile">
<field name="name"/>
<field name="code"/>
<field name="college"/>
@@ -337,33 +335,33 @@
<field name="is_public"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:10px; overflow:hidden; box-shadow:0 2px 6px rgba(255,140,0,.15); border:1px solid rgba(255,140,0,.12);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:10px; overflow:hidden;">
<!-- 顶帽:橙色渐变 + 课程类型徽章 -->
<div style="background:linear-gradient(120deg,#FF9966,#FF5E62); padding:8px 14px; display:flex; align-items:center; justify-content:space-between;">
<div style="color:#fff; font-size:11px; opacity:.92;">
<i class="fa fa-book"/> <field name="code"/>
</div>
<span style="background:rgba(255,255,255,.92); color:#FF5E62; padding:2px 8px; border-radius:10px; font-size:10px; font-weight:600;">
<span class="college-kanban-badge college-kanban-badge-type">
<field name="course_type"/>
</span>
</div>
<!-- 主体 -->
<div style="padding:12px 14px; background:#fff;">
<div style="font-size:14px; font-weight:600; color:#333; margin-bottom:8px; min-height:36px;">
<div class="college-kanban-body" style="padding:12px 14px;">
<div class="college-kanban-title" style="min-height:36px;">
<field name="name"/>
</div>
<div class="text-muted small mb-2">
<div class="college-kanban-meta text-muted small mb-2">
<i class="fa fa-calendar"/> <field name="semester"/>
</div>
<div class="small mb-1">
<div class="college-kanban-meta small mb-1">
<i class="fa fa-user text-muted"/> <field name="teacher_id" widget="many2one_avatar_user"/>
</div>
<div style="display:flex; gap:8px; margin-top:8px;">
<span style="background:#FFF3E0; color:#E65100; padding:3px 10px; border-radius:12px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-hours">
<i class="fa fa-clock-o"/> <field name="hours"/> 课时
</span>
<span style="background:#E3F2FD; color:#1565C0; padding:3px 10px; border-radius:12px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-chapters">
<i class="fa fa-list"/> <field name="chapter_count"/> 章
</span>
</div>
+9 -9
View File
@@ -146,7 +146,7 @@
<field name="arch" type="xml">
<kanban default_group_by="state"
default_order="due_date desc, id"
class="o_kanban_mobile">
class="college-course-kanban college-kanban-homework o_kanban_mobile">
<field name="assignment_id"/>
<field name="student_id"/>
<field name="score"/>
@@ -155,28 +155,28 @@
<field name="submit_time"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:8px; overflow:hidden; box-shadow:0 1px 4px rgba(245,101,101,.15); border:1px solid rgba(245,101,101,.12);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:8px; overflow:hidden;">
<!-- 顶帽:珊瑚渐变 + 得分 -->
<div style="background:linear-gradient(135deg,#F56565,#F6AD55); padding:10px 14px; display:flex; align-items:center; justify-content:space-between;">
<div style="color:#fff; font-size:13px; font-weight:600; max-width:70%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
<div class="college-kanban-title" style="color:#fff; font-size:13px; max-width:70%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
<field name="assignment_id"/>
</div>
<span style="background:rgba(255,255,255,.9); color:#C53030; padding:2px 10px; border-radius:10px; font-size:12px; font-weight:700;">
<span class="college-kanban-badge college-kanban-badge-score">
<field name="score"/> 分
</span>
</div>
<!-- 主体 -->
<div style="padding:10px 14px; background:#fff;">
<div class="small mb-2" groups="college.group_teacher">
<div class="college-kanban-body" style="padding:10px 14px;">
<div class="college-kanban-meta small mb-2" groups="college.group_teacher">
<i class="fa fa-user text-muted"/> <field name="student_id"/>
</div>
<div class="mb-2" style="display:flex; gap:6px; flex-wrap:wrap;">
<span style="background:#FED7D7; color:#C53030; padding:2px 8px; border-radius:8px; font-size:10px; font-weight:600;">
<span class="college-kanban-badge college-kanban-badge-state">
<field name="state"/>
</span>
</div>
<div class="small text-muted" style="line-height:1.5;">
<div class="college-kanban-meta small text-muted" style="line-height:1.5;">
<div><i class="fa fa-calendar-check-o"/> 截止:<field name="due_date"/></div>
<t t-if="record.submit_time.raw_value">
<div><i class="fa fa-clock-o"/> 提交:<field name="submit_time"/></div>
+10 -10
View File
@@ -139,7 +139,7 @@ else:
<field name="name">yuthon.school.progress.kanban</field>
<field name="model">yuthon.school.progress</field>
<field name="arch" type="xml">
<kanban default_group_by="status" class="o_kanban_mobile">
<kanban default_group_by="status" class="college-course-kanban college-kanban-progress o_kanban_mobile">
<field name="course_id"/>
<field name="student_id"/>
<field name="teacher_id"/>
@@ -150,28 +150,28 @@ else:
<field name="status"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:8px; overflow:hidden; box-shadow:0 1px 4px rgba(67,233,123,.15); border:1px solid rgba(67,233,123,.12);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:8px; overflow:hidden;">
<div style="background:linear-gradient(135deg,#2D6A4F,#52B788); padding:10px 14px; display:flex; align-items:center; justify-content:space-between;">
<div style="color:#fff; font-size:14px; font-weight:600;">
<div class="college-kanban-title" style="color:#fff; font-size:14px;">
<field name="course_id"/>
</div>
<span style="background:rgba(255,255,255,.92); color:#1B4332; padding:2px 10px; border-radius:10px; font-size:12px; font-weight:700;">
<span class="college-kanban-badge college-kanban-badge-score">
<field name="score"/> 分
</span>
</div>
<div style="padding:10px 14px; background:#fff;">
<div class="small mb-2" groups="college.group_teacher">
<div class="college-kanban-body" style="padding:10px 14px;">
<div class="college-kanban-meta small mb-2" groups="college.group_teacher">
<i class="fa fa-user text-muted"/> <field name="student_id"/>
</div>
<div class="mb-2">
<div style="display:flex; align-items:center; gap:8px;">
<span class="small text-muted">章节 <field name="chapter_done"/>/<field name="chapter_total"/></span>
<span class="college-kanban-meta small text-muted">章节 <field name="chapter_done"/>/<field name="chapter_total"/></span>
<span style="flex:1;"/>
<span class="small" style="color:#1B4332; font-weight:600;"><field name="progress_rate"/>%</span>
<span class="college-kanban-progress-rate small"><field name="progress_rate"/>%</span>
</div>
</div>
<div class="small text-muted">
<div class="college-kanban-meta small text-muted">
<i class="fa fa-user-circle"/> <field name="teacher_id"/>
</div>
</div>
+8 -8
View File
@@ -4,31 +4,31 @@
<field name="name">yuthon.school.training.kanban</field>
<field name="model">yuthon.school.training</field>
<field name="arch" type="xml">
<kanban default_group_by="name" class="o_kanban_mobile">
<kanban default_group_by="name" class="college-course-kanban college-kanban-training o_kanban_mobile">
<field name="name"/>
<field name="note"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"
style="padding:0; border-radius:10px; overflow:hidden; box-shadow:0 2px 8px rgba(139,92,246,.18); border:1px solid rgba(139,92,246,.1);">
<div class="oe_kanban_global_click college-kanban-card"
style="padding:0; border-radius:10px; overflow:hidden;">
<!-- 顶帽:紫色渐变 -->
<div style="background:linear-gradient(135deg,#8B5CF6,#D946EF); padding:14px; text-align:center;">
<div style="font-size:28px; opacity:.25; color:#fff; margin-bottom:4px;">🏋️</div>
<div style="color:#fff; font-size:16px; font-weight:700;">
<div class="college-kanban-title" style="color:#fff; font-size:16px;">
<field name="name"/>
</div>
</div>
<!-- 主体 -->
<div style="padding:12px 14px; background:#fff;">
<div class="college-kanban-body" style="padding:12px 14px;">
<div style="display:flex; gap:6px; flex-wrap:wrap;">
<span style="background:#F3E8FF; color:#7C3AED; padding:4px 10px; border-radius:10px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-purple">
<i class="fa fa-graduation-cap"/> 实训中
</span>
<span style="background:#FDF2F8; color:#BE185D; padding:4px 10px; border-radius:10px; font-size:11px;">
<span class="college-kanban-badge college-kanban-badge-pink">
<i class="fa fa-clock-o"/> 进行中
</span>
</div>
<div class="text-muted small mt-2" style="line-height:1.4; max-height:40px; overflow:hidden;">
<div class="college-kanban-meta text-muted small mt-2" style="line-height:1.4; max-height:40px; overflow:hidden;">
<field name="note"/>
</div>
</div>