Files

135 lines
5.3 KiB
JavaScript
Raw Permalink Normal View History

2026-09-14 10:23:36 +08:00
/* 通用工具:格式化、选项常量、跨模块下拉数据缓存、图标
所有页面共用,先于 api.js 和页面脚本加载 */
// 本文件是第一个加载的脚本,把 Vue 的常用 API 解构到全局,
// 后面的 api.js / 各页面 / app.js 直接引用 ref、computed 这些名字即可。
// (各文件都是普通 <script>,顶层 const 处于同一个全局作用域)
const { createApp, ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } = Vue;
// 页面在这里注册自己,app.js 最后加载时统一挂载(顺序即左侧菜单顺序)
window.PAGES = window.PAGES || [];
/* ---------- 格式化 ---------- */
// 把空值统一显示成占位符,表格里不会出现难看的 null / 空单元格
function dash(v) {
return (v === null || v === undefined || v === '') ? '—' : v;
}
// "2026-09-01T00:00:00" -> "2026-09-01"
function fmtDate(s) {
if (!s) return '';
return String(s).slice(0, 10);
}
// "2026-09-01T14:57:12" -> "2026-09-01 14:57"
function fmtDateTime(s) {
if (!s) return '';
const t = String(s).replace('T', ' ');
return t.length >= 16 ? t.slice(0, 16) : t;
}
// 提交前去掉空值:没填的字段干脆不下发,
// 这样后端的 exclude_none 和数据库默认值(如薪资 15000)才有机会生效
function pruneEmpty(obj) {
const out = {};
Object.entries(obj || {}).forEach(([k, v]) => {
if (v !== undefined && v !== null && v !== '') out[k] = v;
});
return out;
}
/* ---------- 选项常量(和数据库里实际存的值保持一致)---------- */
const GENDER_OPTIONS = ['男', '女'];
const ROLE_OPTIONS = ['班主任', '任课'];
const EDU_OPTIONS = ['大专', '本科', '硕士', '博士', '其他'];
// 统计模块的年龄比较条件 -> 中文标签
const AGE_OP_OPTIONS = [
{ value: 'gt', label: '大于' },
{ value: 'ge', label: '大于等于' },
{ value: 'lt', label: '小于' },
{ value: 'le', label: '小于等于' },
{ value: 'eq', label: '等于' },
{ value: 'between', label: '区间' },
];
/* ---------- 统一提示 ---------- */
function toastOk(msg) { ElementPlus.ElMessage.success(msg); }
function toastErr(e) { ElementPlus.ElMessage.error(typeof e === 'string' ? e : (e && e.message) || '操作失败'); }
/* ---------- 跨模块下拉数据缓存 ----------
学生表单要班级和顾问,老师表单要班级,就业和成绩要学生,
这些列表在多个页面反复用,缓存一份,避免每次开弹窗都请求一遍。
注意:/classes/ 的 limit 要显式给大值,默认 100 会把列表截断。 */
const Cache = {
classes: [], advisors: [], students: [], loaded: {},
async loadClasses(force) {
if (this.loaded.classes && !force) return this.classes;
this.classes = await ClassApi.list({ limit: 1000 });
this.loaded.classes = true;
return this.classes;
},
async loadAdvisors(force) {
if (this.loaded.advisors && !force) return this.advisors;
this.advisors = await AdvisorApi.list({ limit: 1000 });
this.loaded.advisors = true;
return this.advisors;
},
async loadStudents(force) {
if (this.loaded.students && !force) return this.students;
this.students = await StudentApi.list({});
this.loaded.students = true;
return this.students;
},
// 数据被增删改之后,让缓存失效,下次用会重新拉
invalidate(what) {
if (what) delete this.loaded[what];
else this.loaded = {};
},
};
// 班级表没有班级名列,只有编号和开课时间,所以统一显示成 "编号1 · 2026-09-01 开班"
function className(id) {
if (id === null || id === undefined) return '—';
const c = Cache.classes.find(x => x.class_id === id);
if (!c) return `编号${id}`;
return `编号${id} · ${fmtDate(c.start_time)} 开班`;
}
// 下拉框里更短的写法,表格里再用长的
function classLabel(id) {
if (id === null || id === undefined) return '未分班';
const c = Cache.classes.find(x => x.class_id === id);
return c ? `编号${id} · ${fmtDate(c.start_time)}` : `编号${id}`;
}
function advisorName(id) {
if (id === null || id === undefined) return '—';
const a = Cache.advisors.find(x => x.adv_id === id);
return a ? a.adv_name : `编号${id}`;
}
// 学生下拉框的显示文案:学号 + 姓名
function studentLabel(s) {
return `${s.stu_no} ${dash(s.stu_name)}`;
}
/* ---------- 侧边栏图标(内联 SVG,避免再多引一个图标库)---------- */
const ICONS = {
dashboard: '<path d="M3 3h7v7H3zM14 3h7v4h-7zM14 10h7v11h-7zM3 13h7v8H3z"/>',
student: '<path d="M12 3 2 8l10 5 10-5-10-5z"/><path d="M6 10.5V16c0 1.7 2.7 3 6 3s6-1.3 6-3v-5.5"/>',
class: '<path d="M3 6h18v13H3z"/><path d="M3 10h18M8 6V4h8v2"/>',
teacher: '<circle cx="12" cy="8" r="3.5"/><path d="M4.5 20c0-3.6 3.4-6 7.5-6s7.5 2.4 7.5 6"/>',
advisor: '<circle cx="9" cy="8" r="3"/><path d="M2.5 19c0-3.2 2.9-5.5 6.5-5.5s6.5 2.3 6.5 5.5"/><path d="M17 8h5M19.5 5.5v5"/>',
emp: '<path d="M3 7h18v13H3z"/><path d="M9 7V4.5A1.5 1.5 0 0 1 10.5 3h3A1.5 1.5 0 0 1 15 4.5V7M3 12h18"/>',
score: '<path d="M4 19V5M4 19h16"/><path d="M8 15l3.5-4 3 2.5L20 7"/>',
};
function icon(name) {
return `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">${ICONS[name] || ''}</svg>`;
}