Files
2026-09-14 10:23:36 +08:00

135 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 通用工具:格式化、选项常量、跨模块下拉数据缓存、图标
所有页面共用,先于 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>`;
}