You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
20 KiB
342 lines
20 KiB
|
5 hours ago
|
/* 数据字典 v2 原型 mock(纯前端,无真实后端调用)
|
||
|
|
* 口径来源:.scratch/dict-alignment/issues/08-prototype-v2-contract.md(20260908 拍板)
|
||
|
|
* Q1 /page 行内嵌 children(含停用);Q2 扩展 saveOrUpdate,children 非空即复合;
|
||
|
|
* 子项全量替换(带 id 更新 / 不带 id 新建 / 缺失删除,63009/63003 拦);
|
||
|
|
* Q3 键值=名称仅复合路径新建行(单项新增与编辑维持现状 63001);
|
||
|
|
* Q4 isDefault 任意行可带,落 DictGroupDefault,同载荷仅一行 true。 */
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
/* ================= mock 数据 ================= */
|
||
|
|
let _id = 9000;
|
||
|
|
const nid = () => 'n' + (++_id);
|
||
|
|
const genCode = () => 'dict_' + Math.random().toString(16).slice(2, 10);
|
||
|
|
|
||
|
|
const STORE = {
|
||
|
|
groups: [
|
||
|
|
{ id: 'g1', name: '行业', code: 'industry', status: 1 },
|
||
|
|
{ id: 'g2', name: '客户类型', code: 'customer_type', status: 1 },
|
||
|
|
],
|
||
|
|
defaultItemId: { g1: 'i1', g2: 'i2' }, // groupId -> 默认项 id(DictGroupDefault 模拟,一组一个)
|
||
|
|
items: [ // 顶层只挂一级项;二级项全部内嵌在 children(= /page v2 契约)
|
||
|
|
{ id: 'i1', groupId: 'g1', name: '互联网/信息技术', code: 'internet', value: '互联网/信息技术',
|
||
|
|
sortNo: 1, status: 1, description: '', builtin: false, referenced: false,
|
||
|
|
children: [
|
||
|
|
{ id: 'i101', name: '电商', code: 'elec', value: '电商', sortNo: 1, status: 1, referenced: false },
|
||
|
|
{ id: 'i102', name: '游戏', code: 'game', value: '游戏', sortNo: 2, status: 1, referenced: true },
|
||
|
|
{ id: 'i103', name: '社交(停用示例)', code: 'social', value: '社交', sortNo: 3, status: 0, referenced: false },
|
||
|
|
] },
|
||
|
|
{ id: 'i3', groupId: 'g1', name: '金融', code: 'finance', value: '金融', sortNo: 2, status: 1, description: '', builtin: false, referenced: false,
|
||
|
|
children: [
|
||
|
|
{ id: 'i301', name: '银行', code: 'bank', value: '银行', sortNo: 1, status: 1, referenced: false },
|
||
|
|
{ id: 'i302', name: '保险', code: 'insurance', value: '保险', sortNo: 2, status: 1, referenced: false },
|
||
|
|
] },
|
||
|
|
{ id: 'i4', groupId: 'g1', name: '制造业(无子项示例)', code: 'manufacture', value: '制造业', sortNo: 3, status: 1, description: '', builtin: false, referenced: false, children: [] },
|
||
|
|
{ id: 'i2', groupId: 'g2', name: '潜在客户', code: 'potential', value: '潜在客户', sortNo: 1, status: 1, description: '', builtin: false, referenced: true, children: [] },
|
||
|
|
{ id: 'i5', groupId: 'g2', name: '重点客户', code: 'key_account', value: '重点客户', sortNo: 2, status: 1, description: '', builtin: false, referenced: false, children: [] },
|
||
|
|
{ id: 'i6', groupId: 'g2', name: '一般客户', code: 'normal', value: '一般客户', sortNo: 3, status: 1, description: '', builtin: false, referenced: false, children: [] },
|
||
|
|
],
|
||
|
|
};
|
||
|
|
const $ = id => document.getElementById(id);
|
||
|
|
const esc = s => s == null ? '' : String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||
|
|
const expanded = new Set();
|
||
|
|
const groupOf = id => STORE.groups.find(g => g.id === id);
|
||
|
|
const isDefaultItem = it => STORE.defaultItemId[it.groupId] === it.id;
|
||
|
|
|
||
|
|
function toast(msg, type) {
|
||
|
|
const d = document.createElement('div');
|
||
|
|
d.className = 'toast-item toast-' + (type === 'ok' ? 'ok' : type === 'err' ? 'err' : 'info');
|
||
|
|
d.textContent = msg;
|
||
|
|
$('toast').appendChild(d);
|
||
|
|
setTimeout(() => d.remove(), 6000);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ================= 列表(只出一级 + 行内嵌子项) ================= */
|
||
|
|
function visibleRows() {
|
||
|
|
const g = $('iGroup').value, kw = ($('iKeyword').value || '').trim(), st = $('iStatus').value;
|
||
|
|
return STORE.items.filter(i =>
|
||
|
|
(!g || i.groupId === g) &&
|
||
|
|
(!st || String(i.status) === st) &&
|
||
|
|
(!kw || (i.name || '').includes(kw) || (i.code || '').includes(kw))); // 筛选只作用一级行
|
||
|
|
}
|
||
|
|
const statusBadge = s => s === 1 ? '<span class="badge b2">启用</span>' : '<span class="badge b4">停用</span>';
|
||
|
|
function marks(it) {
|
||
|
|
const g = groupOf(it.groupId);
|
||
|
|
return (it.referenced ? '<span class="badge b3">被引用</span>' : '') +
|
||
|
|
(it.status === 1 && g && g.status === 0 ? '<span class="badge b1">失活</span>' : '');
|
||
|
|
}
|
||
|
|
function renderItems() {
|
||
|
|
const rows = visibleRows();
|
||
|
|
$('iBody').innerHTML = rows.length ? rows.map(i => {
|
||
|
|
const kids = i.children || [];
|
||
|
|
const open = expanded.has(i.id);
|
||
|
|
const chev = '<span class="expander' + (kids.length ? (open ? ' on' : '') : ' none') +
|
||
|
|
'" ' + (kids.length ? 'onclick="toggleExpand(\'' + i.id + '\')" title="展开/收起二级子项"' : 'title="无子项"') + '>' + (open ? '▼' : '▶') + '</span>';
|
||
|
|
let html = '<tr>' +
|
||
|
|
'<td>' + chev + '</td>' +
|
||
|
|
'<td>' + esc(groupOf(i.groupId).name) + '</td>' +
|
||
|
|
'<td>' + esc(i.name) + (kids.length ? '<span class="childcount">' + kids.length + ' 个子项</span>' : '') + '</td>' +
|
||
|
|
'<td>' + esc(i.code) + '</td><td>' + esc(i.value) + '</td><td>' + esc(i.sortNo) + '</td>' +
|
||
|
|
'<td>' + statusBadge(i.status) + '</td>' +
|
||
|
|
'<td>' + (isDefaultItem(i) ? '<span class="badge b5">默认</span>' : '<span class="mark">否</span>') + '</td>' +
|
||
|
|
'<td>' + marks(i) + '</td>' +
|
||
|
|
'<td class="ops">' +
|
||
|
|
'<button onclick="dlgItem(\'' + i.id + '\')">编辑</button>' +
|
||
|
|
'<button onclick="itemStatus(\'' + i.id + '\')">' + (i.status === 1 ? '停用' : '启用') + '</button>' +
|
||
|
|
'<button onclick="itemSetDefault(\'' + i.id + '\')">设默认</button>' +
|
||
|
|
'<button class="warn" onclick="itemDelete(\'' + i.id + '\')">删除</button></td></tr>';
|
||
|
|
if (open) {
|
||
|
|
html += kids.length ? kids.map(c => '<tr class="lv2row">' +
|
||
|
|
'<td></td><td></td>' +
|
||
|
|
'<td>└ ' + esc(c.name) + ' <span class="mark">二级</span></td>' +
|
||
|
|
'<td>' + esc(c.code) + '</td><td>' + esc(c.value) + '</td><td>' + esc(c.sortNo) + '</td>' +
|
||
|
|
'<td>' + statusBadge(c.status) + '</td>' +
|
||
|
|
'<td>' + (isDefaultItem(c) ? '<span class="badge b5">默认</span>' : '<span class="mark">否</span>') + '</td>' +
|
||
|
|
'<td>' + (c.referenced ? '<span class="badge b3">被引用</span>' : '') + '</td>' +
|
||
|
|
'<td class="ops"><span class="hint">随一级项「编辑」弹窗全量维护</span></td></tr>').join('')
|
||
|
|
: '<tr class="lv2row"><td></td><td></td><td colspan="8" class="hint">(无二级子项)</td></tr>';
|
||
|
|
}
|
||
|
|
return html;
|
||
|
|
}).join('') : '<tr><td colspan="10" class="empty">暂无字典项</td></tr>';
|
||
|
|
}
|
||
|
|
function toggleExpand(id) { expanded.has(id) ? expanded.delete(id) : expanded.add(id); renderItems(); }
|
||
|
|
|
||
|
|
/* ================= 子项行(弹窗内列表) ================= */
|
||
|
|
function kidRowHtml(k) {
|
||
|
|
const exist = !k._new;
|
||
|
|
return '<tr data-kid="' + esc(k._tmp || k.id || '') + '" data-new="' + (k._new ? 1 : 0) + '" class="' + (k._del ? 'ghost' : '') + '">' +
|
||
|
|
'<td><input value="' + esc(k.name) + '" placeholder="必填"></td>' +
|
||
|
|
'<td><input value="' + esc(k.code) + '" ' + (exist ? 'readonly style="background:#f5f7fa"' : '') + ' placeholder="留空自动生成"></td>' +
|
||
|
|
'<td style="text-align:center"><input type="checkbox" class="kdef" ' + (k._def ? 'checked' : '') + '></td>' +
|
||
|
|
'<td><input class="ksort" value="' + esc(k.sortNo == null ? '' : k.sortNo) + '" style="width:56px"></td>' +
|
||
|
|
'<td class="vhint">' + (exist ? '现值 ' + esc(k.value) + '(更新行键值不变)' : '= 名称(后端落值)') + '</td>' +
|
||
|
|
'<td>' + (exist ? '<span class="hint">带 id 更新</span> ' : '<span class="hint">新建</span> ') +
|
||
|
|
'<button class="delrow" onclick="delKidRow(this)">' + (k._del ? '恢复' : '删行') + '</button></td></tr>';
|
||
|
|
}
|
||
|
|
function readKidRows() {
|
||
|
|
return [...document.querySelectorAll('#kidsBody tr')]
|
||
|
|
.filter(tr => tr.querySelector('input'))
|
||
|
|
.map(tr => ({
|
||
|
|
_tmp: tr.dataset.kid,
|
||
|
|
_new: tr.dataset.new === '1',
|
||
|
|
name: tr.querySelectorAll('input')[0].value.trim(),
|
||
|
|
code: tr.querySelectorAll('input')[1].value.trim(),
|
||
|
|
_def: tr.querySelector('.kdef').checked,
|
||
|
|
sortNo: tr.querySelector('.ksort').value.trim(),
|
||
|
|
_del: tr.classList.contains('ghost'),
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
function repaintKids(kids) {
|
||
|
|
$('kidsBody').innerHTML = kids.map(kidRowHtml).join('') || '<tr><td colspan="6" class="hint">暂无二级子项,点上方按钮添加</td></tr>';
|
||
|
|
syncValueField();
|
||
|
|
}
|
||
|
|
function addKidRow() {
|
||
|
|
const kids = readKidRows();
|
||
|
|
kids.push({ _tmp: nid(), _new: true, name: '', code: '', _def: false, sortNo: kids.filter(k => !k._del).length + 1, _del: false });
|
||
|
|
repaintKids(kids);
|
||
|
|
}
|
||
|
|
function delKidRow(btn) {
|
||
|
|
const tr = btn.closest('tr');
|
||
|
|
const kids = readKidRows();
|
||
|
|
const idx = [...document.querySelectorAll('#kidsBody tr')].indexOf(tr);
|
||
|
|
kids[idx]._del = !kids[idx]._del;
|
||
|
|
repaintKids(kids);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ================= 键值字段联动(Q3 口径的可视化) ================= */
|
||
|
|
function syncValueField() {
|
||
|
|
const v = $('fiValue'); if (!v) return;
|
||
|
|
const hint = $('fiValueHint');
|
||
|
|
const liveKids = readKidRows().filter(k => !k._del).length;
|
||
|
|
if (window._editId) {
|
||
|
|
v.disabled = false;
|
||
|
|
hint.textContent = '编辑:维持现状,传了就改(63001 照走);子项新建行不传、后端取名称';
|
||
|
|
} else if (liveKids > 0) {
|
||
|
|
v.disabled = true; v.value = '';
|
||
|
|
v.placeholder = '复合新增:不传键值,后端取字典名称落值';
|
||
|
|
hint.textContent = '已有 ' + liveKids + ' 个二级子项 → 复合新增:不传键值,后端直接取字典名称落值';
|
||
|
|
} else {
|
||
|
|
v.disabled = false;
|
||
|
|
v.placeholder = '必填,空白报 63001(单项新增现状)';
|
||
|
|
hint.textContent = '无子项 → 单项新增现状:键值必填(空白 63001)';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ================= 复合新增/编辑弹窗 ================= */
|
||
|
|
function dlgItem(id) {
|
||
|
|
window._editId = id || '';
|
||
|
|
const it = id ? STORE.items.find(x => x.id === id) : null;
|
||
|
|
const gOpts = STORE.groups.map(g => '<option value="' + g.id + '" ' + (it && it.groupId === g.id ? 'selected' : '') + '>' +
|
||
|
|
esc(g.name) + '(' + esc(g.code) + ')</option>').join('');
|
||
|
|
const kids = it ? (it.children || []).map(c => Object.assign({}, c, { _def: STORE.defaultItemId[it.groupId] === c.id })) : [];
|
||
|
|
$('dlgTitle').textContent = it
|
||
|
|
? '编辑字典项(children 全量替换:带 id 更新 / 不带 id 新建 / 缺失删除)'
|
||
|
|
: '新建字典项(有子项=复合新增,键值=名称;无子项=单项现状)';
|
||
|
|
$('dlgBody').innerHTML = `
|
||
|
|
<label><b>所属分组*</b><select id="fiGroup">${gOpts}</select></label>
|
||
|
|
<label><b>字典名称*</b><input id="fiName" value="${esc(it && it.name)}"></label>
|
||
|
|
<label><b>字典键值</b><input id="fiValue" value="${esc(it ? it.value : '')}" placeholder="必填,空白报 63001(拍板3)"><span class="hint" id="fiValueHint"></span></label>
|
||
|
|
<label><b>字典编码</b><input id="fiCode" value="${esc(it && it.code)}" ${it ? 'readonly style="background:#f5f7fa"' : ''} placeholder="留空自动生成 dict_*;创建后只读(63008)"></label>
|
||
|
|
<label><b>排序号</b><input id="fiSort" value="${esc(it ? (it.sortNo == null ? '' : it.sortNo) : '')}"></label>
|
||
|
|
<label><b>状态</b><select id="fiStatus"><option value="1" ${!it || it.status === 1 ? 'selected' : ''}>启用</option><option value="0" ${it && it.status === 0 ? 'selected' : ''}>停用</option></select>
|
||
|
|
<span class="hint">停用分组默认项 → 63013(口径沿用)</span></label>
|
||
|
|
<label><b>设为分组默认项</b><input type="checkbox" id="fiDefault" ${it && isDefaultItem(it) ? 'checked' : ''}>
|
||
|
|
<span class="hint">与子项「是否默认值」同口径:同载荷仅一行 true(一组一个默认,自动取消原默认)</span></label>
|
||
|
|
<label><b>描述</b><textarea id="fiDesc" rows="2">${esc(it && it.description)}</textarea></label>
|
||
|
|
<div class="kids">
|
||
|
|
<div class="khead"><b>二级字典项</b><span class="hint">列表形式随本接口一次提交(children[i].xxx 表单绑定)</span>
|
||
|
|
<span style="flex:1"></span><button class="act" onclick="addKidRow()">+ 添加二级字典项</button></div>
|
||
|
|
<table><thead><tr><th>字典名称*</th><th>字典编码</th><th>是否默认值</th><th>排序</th><th>键值</th><th></th></tr></thead>
|
||
|
|
<tbody id="kidsBody">${kids.map(kidRowHtml).join('') || '<tr><td colspan="6" class="hint">暂无二级子项,点上方按钮添加</td></tr>'}</tbody></table>
|
||
|
|
<div class="hint">「删行」置灰划线 = 全量替换中的「缺失即删除」;被引用(63009)/内置(63003) 项后端拦截。新建行键值=名称;带 id 更新行键值不变。</div>
|
||
|
|
</div>
|
||
|
|
<div id="fiOut"></div>`;
|
||
|
|
$('dlgFoot').innerHTML = '<button onclick="dlgClose()">取消</button><button class="ok" onclick="saveItem(\'' + (id || '') + '\')">保存</button>';
|
||
|
|
$('dlg').showModal();
|
||
|
|
syncValueField();
|
||
|
|
}
|
||
|
|
function dlgClose() { $('dlg').close(); }
|
||
|
|
|
||
|
|
/* ================= 保存(复合语义 + 守卫演示) ================= */
|
||
|
|
function payloadPreview(id, p, singleMode) {
|
||
|
|
const g = groupOf(p.groupId);
|
||
|
|
const L = [];
|
||
|
|
L.push('POST /api/dict/item/saveOrUpdate' + (id ? '?id=' + id : '') +
|
||
|
|
(singleMode ? ' (children 空 → 单项保存现状)' : ' (children 非空 → 复合保存)'));
|
||
|
|
L.push('groupId=' + p.groupId + ' # ' + g.name + '(' + g.code + ')');
|
||
|
|
L.push('name=' + p.name);
|
||
|
|
if (singleMode) L.push('value=' + p.value + ' # 单项新增:键值照传(现状)');
|
||
|
|
else if (id) L.push('value=' + p.value + ' # 编辑:键值照传,传了就改(现状)');
|
||
|
|
else L.push('(复合新增:不传顶层 value——新建行键值=名称)');
|
||
|
|
L.push('code=' + (p.code || '') + (p.code || id ? '' : ' # 空 → 后端自动生成 dict_*'));
|
||
|
|
L.push('sortNo=' + p.sortNo);
|
||
|
|
L.push('status=' + p.status);
|
||
|
|
L.push('isDefault=' + p.isDefault);
|
||
|
|
L.push('description=' + p.description);
|
||
|
|
p.children.forEach((k, i) => {
|
||
|
|
L.push('children[' + i + ']' + (k.id ? '.id=' + k.id + ' # 带 id → 更新(键值维持现值)' : ' # 无 id → 新建(后端落 键值=名称)'));
|
||
|
|
L.push('children[' + i + '].name=' + k.name);
|
||
|
|
L.push('children[' + i + '].code=' + (k.code || '') + (k.code || k.id ? '' : ' # 空 → 后端自动生成'));
|
||
|
|
L.push('children[' + i + '].sortNo=' + k.sortNo);
|
||
|
|
L.push('children[' + i + '].isDefault=' + k.isDefault);
|
||
|
|
});
|
||
|
|
const cur = id ? STORE.items.find(x => x.id === id) : null;
|
||
|
|
if (cur) {
|
||
|
|
const missing = (cur.children || []).filter(c => !p.children.some(k => k.id === c.id));
|
||
|
|
L.push(missing.length
|
||
|
|
? '(本载荷缺失的既有子项 → 删除:' + missing.map(c => c.id + ' ' + c.name).join('、') + ';被引用/内置则 63009/63003 拦)'
|
||
|
|
: '(既有子项全部在载荷中,无删除)');
|
||
|
|
}
|
||
|
|
const box = $('payloadBox');
|
||
|
|
box.style.display = 'block';
|
||
|
|
box.textContent = L.join('\n');
|
||
|
|
$('payloadHint').textContent = '↑ 上一条保存的请求载荷(仅预览,mock 未真正发请求)。Spring 表单绑定集合语法:children[0].name=…';
|
||
|
|
}
|
||
|
|
function saveItem(id) {
|
||
|
|
const it = id ? STORE.items.find(x => x.id === id) : null;
|
||
|
|
const err = m => { $('fiOut').innerHTML = '<div class="errbox">失败: ' + esc(m) + '</div>'; };
|
||
|
|
const name = ($('fiName').value || '').trim();
|
||
|
|
if (!name) return err('字典名称不能为空');
|
||
|
|
const kidsRaw = readKidRows();
|
||
|
|
// 全量替换守卫:缺失(含删行置灰)的既有子项先过 63009/63003
|
||
|
|
if (it) {
|
||
|
|
const kept = kidsRaw.filter(k => !k._del).map(k => k._tmp);
|
||
|
|
const missing = (it.children || []).filter(c => !kept.includes(c.id));
|
||
|
|
for (const c of missing) {
|
||
|
|
if (c.referenced) return err('63009 该字典已被引用,无法删除(子项 ' + c.name + ')——全量替换守卫生效');
|
||
|
|
if (c.builtin) return err('63003 内置字典不可删除(子项 ' + c.name + ')');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const kids = kidsRaw.filter(k => !k._del).map((k, i) => ({
|
||
|
|
id: k._new ? undefined : k._tmp,
|
||
|
|
name: k.name, code: k.code,
|
||
|
|
sortNo: k.sortNo === '' ? i + 1 : Number(k.sortNo),
|
||
|
|
isDefault: k._def,
|
||
|
|
}));
|
||
|
|
for (let i = 0; i < kids.length; i++) if (!kids[i].name) return err('二级字典项名称不能为空(第 ' + (i + 1) + ' 行)');
|
||
|
|
const selfDefault = $('fiDefault').checked;
|
||
|
|
const defCount = (selfDefault ? 1 : 0) + kids.filter(k => k.isDefault).length;
|
||
|
|
if (defCount > 1) return err('同一载荷只允许一行「是否默认值」=是(一组仅一个默认)——当前 ' + defCount + ' 行为 true');
|
||
|
|
if (it && $('fiStatus').value === '0' && STORE.defaultItemId[it.groupId] === it.id) {
|
||
|
|
return err('63013 该字典为分组默认项,请先取消默认或指定其他默认项');
|
||
|
|
}
|
||
|
|
const singleMode = kids.length === 0; // children 空 → 单项保存现状(键值必填)
|
||
|
|
const value = ($('fiValue').value || '').trim();
|
||
|
|
if (it && !value) return err('63001 字典键值不能为空(编辑维持现状口径)');
|
||
|
|
if (!it && singleMode && !value) return err('63001 字典键值不能为空(单项新增现状口径)');
|
||
|
|
const groupId = $('fiGroup').value;
|
||
|
|
const code = ($('fiCode').value || '').trim();
|
||
|
|
const p = {
|
||
|
|
groupId, name, code, value,
|
||
|
|
sortNo: ($('fiSort').value || '').trim(), status: $('fiStatus').value,
|
||
|
|
isDefault: selfDefault, description: ($('fiDesc').value || '').trim(), children: kids,
|
||
|
|
};
|
||
|
|
payloadPreview(id, p, singleMode);
|
||
|
|
// ---- mock 落库(children 全量替换) ----
|
||
|
|
const lvl1Id = it ? it.id : nid();
|
||
|
|
const newKids = kids.map(k => {
|
||
|
|
const old = k.id && it ? (it.children || []).find(c => c.id === k.id) : null;
|
||
|
|
return {
|
||
|
|
id: old ? old.id : nid(),
|
||
|
|
name: k.name,
|
||
|
|
code: old ? old.code : (k.code || genCode()),
|
||
|
|
value: old ? old.value : k.name, // 新建行键值=名称;更新行键值不变
|
||
|
|
sortNo: k.sortNo, status: 1,
|
||
|
|
referenced: old ? old.referenced : false,
|
||
|
|
builtin: old ? old.builtin : false,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
if (it) {
|
||
|
|
Object.assign(it, {
|
||
|
|
groupId, name,
|
||
|
|
sortNo: p.sortNo === '' ? it.sortNo : Number(p.sortNo),
|
||
|
|
status: Number(p.status), description: p.description,
|
||
|
|
value: value, // 编辑:传了就改(现状口径)
|
||
|
|
children: newKids,
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
STORE.items.push({
|
||
|
|
id: lvl1Id, groupId, name,
|
||
|
|
code: code || genCode(),
|
||
|
|
value: singleMode ? value : name, // 单项现状传值;复合新建=名称
|
||
|
|
sortNo: p.sortNo === '' ? undefined : Number(p.sortNo),
|
||
|
|
status: Number(p.status), description: p.description,
|
||
|
|
builtin: false, referenced: false, children: newKids,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const defWinner = selfDefault ? lvl1Id : (kids.find(k => k.isDefault) || {}).id;
|
||
|
|
if (defWinner) STORE.defaultItemId[groupId] = defWinner;
|
||
|
|
toast('保存成功(mock)——children 已按全量替换落库', 'ok');
|
||
|
|
dlgClose();
|
||
|
|
expanded.add(lvl1Id);
|
||
|
|
renderItems();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ================= 行内操作(守卫口径沿用) ================= */
|
||
|
|
function itemStatus(id) {
|
||
|
|
const it = STORE.items.find(x => x.id === id);
|
||
|
|
if (it.status === 1 && STORE.defaultItemId[it.groupId] === it.id) return toast('63013 该字典为分组默认项,请先取消默认或指定其他默认项', 'err');
|
||
|
|
it.status = it.status === 1 ? 0 : 1;
|
||
|
|
toast('状态已更新(mock)', 'ok'); renderItems();
|
||
|
|
}
|
||
|
|
function itemSetDefault(id) {
|
||
|
|
const it = STORE.items.find(x => x.id === id);
|
||
|
|
STORE.defaultItemId[it.groupId] = it.id;
|
||
|
|
toast('已设为分组默认项(同组原默认自动取消,mock)', 'ok'); renderItems();
|
||
|
|
}
|
||
|
|
function itemDelete(id) {
|
||
|
|
const it = STORE.items.find(x => x.id === id);
|
||
|
|
if (it.referenced) return toast('63009 该字典已被引用,无法删除(force-delete 为计数飘高兜底入口,不在本原型展开)', 'err');
|
||
|
|
if ((it.children || []).length) return toast('有存活子项,先在「编辑」弹窗删行保存后再删除本项(口径沿用)', 'err');
|
||
|
|
if (!confirm('确认删除字典项 ' + it.name + '?')) return;
|
||
|
|
STORE.items = STORE.items.filter(x => x.id !== id);
|
||
|
|
toast('已删除(mock)', 'ok'); renderItems();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ================= 初始化 ================= */
|
||
|
|
(function init() {
|
||
|
|
$('iGroup').innerHTML = '<option value="">全部分组</option>' +
|
||
|
|
STORE.groups.map(g => '<option value="' + g.id + '">' + esc(g.name) + '(' + esc(g.code) + ')</option>').join('');
|
||
|
|
expanded.add('i1');
|
||
|
|
renderItems();
|
||
|
|
})();
|