Compare commits

...

2 Commits

Author SHA1 Message Date
mac
83c2a5ca94 Merge branch 'dev'
All checks were successful
Deploy / deploy (push) Successful in 12s
2026-07-02 14:50:45 +08:00
mac
fbff2e5f24 产品迭代:表格排序 + 详情页优先级状态对齐
- 表头点击排序(正序/倒序切换),优先级按 P0-P3 逻辑序
- 排序图标与表头文字同行显示
- 详情页优先级/状态合并行改用标准 drawer-field 结构对齐
2026-07-02 14:50:45 +08:00
2 changed files with 44 additions and 11 deletions

View File

@@ -58,7 +58,7 @@ function openDrawer(resource, id) {
${resource === "operations" ? drawerField("map-pin", "当前阶段", "current_stage", "", false, `<select name="current_stage" class="form-ctrl" onchange="saveDrawerField(this,'${resource}',${id})">${["商务洽谈","系统上线","团队分工","项目交付","上线推广","结项验收"].map((s) => `<option ${s === item.current_stage ? "selected" : ""}>${s}</option>`).join("")}</select>`) : ""}
${fields.map(([key,label]) => {
if (resource === "products" && key === "priority") {
return `<div class="drawer-field"><div class="grid grid-cols-2 gap-3"><div><div class="drawer-field-label"><i data-lucide="flag"></i><span>优先级</span></div><select name="priority" class="form-ctrl" onchange="saveDrawerField(this,'${resource}',${id})">${["P0","P1","P2","P3"].map((s) => `<option ${s === (item.priority||'P2') ? "selected" : ""}>${s}</option>`).join("")}</select></div><div><div class="drawer-field-label"><i data-lucide="circle-dot"></i><span>状态</span></div><select name="status" class="form-ctrl" onchange="saveDrawerField(this,'${resource}',${id})">${["未开始","规划中","开发中","测试中","已上线","已取消"].map((s) => `<option ${s === (item.status||'规划中') ? "selected" : ""}>${s}</option>`).join("")}</select></div></div></div>`;
return `<div class="drawer-field"><div class="drawer-field-label"><i data-lucide="flag"></i><span>优先级 / 状态</span></div><div class="drawer-field-control grid grid-cols-2 gap-3"><select name="priority" class="form-ctrl" onchange="saveDrawerField(this,'${resource}',${id})">${["P0","P1","P2","P3"].map((s) => `<option ${s === (item.priority||'P2') ? "selected" : ""}>${s}</option>`).join("")}</select><select name="status" class="form-ctrl" onchange="saveDrawerField(this,'${resource}',${id})">${["未开始","规划中","开发中","测试中","已上线","已取消"].map((s) => `<option ${s === (item.status||'规划中') ? "selected" : ""}>${s}</option>`).join("")}</select></div></div>`;
}
if (resource === "products" && (key === "start_date" || key === "plan_date" || key === "dev_done_date" || key === "test_date" || key === "launch_date")) {
return drawerField("calendar", label, key, item[key], false, `<input type="date" name="${key}" value="${item[key]||''}" class="form-ctrl" data-original="${item[key]||''}" onchange="saveDrawerField(this,'${resource}',${id})">`);

View File

@@ -151,9 +151,42 @@ window.addFeature = () => {};
window.removeFeature = () => {};
window.saveFeatureList = () => {};
// 排序状态
let productSort = { field: null, dir: 1 };
window.sortProducts = (field) => {
if (productSort.field === field) {
productSort.dir = -productSort.dir;
} else {
productSort.field = field;
productSort.dir = 1;
}
renderProducts();
};
function sortItems(items) {
if (!productSort.field) return items;
const f = productSort.field;
const d = productSort.dir;
const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
return [...items].sort((a, b) => {
let va = a[f] || '', vb = b[f] || '';
if (f === 'priority') { va = priorityOrder[va] ?? 9; vb = priorityOrder[vb] ?? 9; }
if (va < vb) return -1 * d;
if (va > vb) return 1 * d;
return 0;
});
}
function renderProducts() {
const items = state.data.products || [];
const rawItems = state.data.products || [];
const items = sortItems(rawItems);
const priorityColor = { P0: "bg-red-100 text-red-700", P1: "bg-orange-100 text-orange-700", P2: "bg-blue-100 text-blue-700", P3: "bg-slate-100 text-slate-600" };
const sortIcon = (f) => {
if (productSort.field !== f) return '<i data-lucide="chevrons-up-down" style="width:12px;height:12px;opacity:0.3"></i>';
return productSort.dir > 0 ? '<i data-lucide="chevron-up" style="width:12px;height:12px"></i>' : '<i data-lucide="chevron-down" style="width:12px;height:12px"></i>';
};
const sortTh = (f, label, extra='') => `<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap cursor-pointer hover:text-blue-600 select-none" onclick="sortProducts('${f}')"><span class="inline-flex items-center gap-1">${label}${sortIcon(f)}</span>${extra}</th>`;
document.querySelector("#products").innerHTML = `
<div class="grid gap-4">
<div class="flex justify-end">
@@ -163,15 +196,15 @@ function renderProducts() {
<table class="w-full text-sm">
<thead class="bg-slate-50 border-b border-slate-200">
<tr class="text-left text-xs text-slate-500 uppercase tracking-wider">
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">版本号</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">优先级</th>
<th class="px-3 py-2.5 font-semibold align-middle">版本名称</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">状态</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">启动时间</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">产品方案</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">研发完成</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">测试完成</th>
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">上线时间</th>
${sortTh('version','版本号')}
${sortTh('priority','优先级')}
${sortTh('product_name','版本名称')}
${sortTh('status','状态')}
${sortTh('start_date','启动时间')}
${sortTh('plan_date','产品方案')}
${sortTh('dev_done_date','研发完成')}
${sortTh('test_date','测试完成')}
${sortTh('launch_date','上线时间')}
<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap">总耗时</th>
</tr>
</thead>