定开项目:改名(华智下单/进行中)+ 表格排序与筛选 + 列序/按钮调整
项目归属「新签订单」→「华智下单」(含数据迁移 83 条,导入脚本自动映射) 项目状态「交付中」→「进行中」(含数据迁移 10 条,导入脚本自动映射);项目归属去掉「其他」选项 本周进展卡片:本周新增项目→新增项目、交付项目→进行中项目 清空早于 2026-09-18 的「更新时间」(111 条),后续编辑按提交时间重新写入 表格:备注与更新时间列对调;下单时间/订单金额/更新时间 表头支持升/降序 去掉「删除本周更新」按钮(后端接口保留) 新增「推动验收计划」筛选,导出同步(export_custom 新增 accept_plan)develop-chezhiqi
parent
a77cd7bc81
commit
07e1b0ae7b
|
|
@ -60,3 +60,37 @@
|
||||||
- 定开第一个 tab **「项目明细」→「项目总览」**(chip 与卡片标题「定开项目总览」都改)。
|
- 定开第一个 tab **「项目明细」→「项目总览」**(chip 与卡片标题「定开项目总览」都改)。
|
||||||
- `customTableHtml` 列序:把**「项目归属」移到「下单时间」之后**(新序:项目名称 / 项目ID / 下单时间 / 项目归属 / 办事处 / …)。项目总览与本周进展两个 tab 共用,故同时生效。
|
- `customTableHtml` 列序:把**「项目归属」移到「下单时间」之后**(新序:项目名称 / 项目ID / 下单时间 / 项目归属 / 办事处 / …)。项目总览与本周进展两个 tab 共用,故同时生效。
|
||||||
- 后端导出标题仍是「定开项目明细」(未改,用户只说 tab 名)。纯前端改动,无需重启。
|
- 后端导出标题仍是「定开项目明细」(未改,用户只说 tab 名)。纯前端改动,无需重启。
|
||||||
|
|
||||||
|
## 推送到 develop-chezhiqi
|
||||||
|
- 仓库此前**未配置 git 身份**(唯一提交 e9854eb 作者是 mula.liu <mula.liu@163.com>)。按用户选择,设**本仓库** `user.name=chezhiqi` / `user.email=chezhiqi@unissense.tech`(邮箱取远端域名 unissense.tech,如不对可 amend)。
|
||||||
|
- 提交 **a77cd7b**「定开项目:台账化改造 + 统计/导出/搜索调整,新增本周进展与年度任务 tab」,包含 app/**、web/**、scripts/**(新增 `import_custom_ledger.py`)、README.md、run.sh、`data/board.db`、`.workbuddy/memory/**`(含被 git 跟踪的 `__pycache__/*.pyc`)。
|
||||||
|
- 已 push 到 `origin/develop-chezhiqi`(`e9854eb..a77cd7b`),本地与远端一致(远端还提示可发 PR main...develop-chezhiqi)。
|
||||||
|
- **有意未提交**:`data/board.db.bak-*` 4 个本地备份(保持 untracked)。
|
||||||
|
|
||||||
|
## 「项目归属」新签订单 → 华智下单
|
||||||
|
- 前端 `web/app.js`:`CUSTOM_ORIGINS` / `CUSTOM_PLACED` 里 `新签订单` → **`华智下单`**(连同新建对话框默认值、统计说明文案,全文替换 6 处,现 app.js 内已无「新签订单」)。
|
||||||
|
- **数据迁移**:`UPDATE custom_projects SET origin='华智下单' WHERE origin='新签订单'`(83 行);备份 `data/board.db.bak-before-origin-rename-20260920_213304`。现 origin 分布:华智下单 83 / 存量项目 27 / 汇智直签 10 / 提前交付 2。
|
||||||
|
- `scripts/import_custom_ledger.py`:读入行时把 `新签订单` 归一化为 `华智下单`(防再导入回退),并更新文档注释;`app/models.py` 注释同步。
|
||||||
|
- 纯前端 + 数据 + 脚本改动,**无需重启**(models.py 仅注释)。**本次未 push**(用户本轮只要求改名)。
|
||||||
|
|
||||||
|
## 三项改名/调整(项目状态 / 本周卡片 / 归属选项)
|
||||||
|
1. **项目状态「交付中」→「进行中」**(全量改名):
|
||||||
|
- `web/app.js` 全文 `交付中`→`进行中`(共 12 处:状态色映射、STATUS_OPTIONS、进度列判定、本周「交付项目」= 进行中+交付完成、新建/编辑 statusOpts、placeholder、注释)。
|
||||||
|
- **数据迁移** `UPDATE custom_projects SET project_status='进行中' WHERE project_status='交付中'`(10 行);备份 `data/board.db.bak-before-status-rename-20260920_213707`。现状态:已验收 64 / 待开发 29 / 交付完成 17 / **进行中 10** / 退单 2。
|
||||||
|
- `import_custom_ledger.py` 读入时 `交付中`→`进行中` 归一化;`models.py` 注释同步。
|
||||||
|
2. **本周进展卡片「本周新增项目」→「新增项目」**(`web/app.js` 卡片标题 + 说明文案)。
|
||||||
|
3. **项目归属去掉「其他」选项**:`CUSTOM_ORIGINS = [华智下单, 存量项目, 汇智直签, 提前交付]`(新建/筛选下拉都不再有「其他」);`models.py` / 导入脚本文档注释同步。数据中本来就没有 origin=其他 的行。
|
||||||
|
- 纯前端 + 数据 + 脚本改动,**无需重启**;`node --check` / `ast.parse` 通过。**本轮未 push**。
|
||||||
|
|
||||||
|
## 本周卡片改名 + 清空历史「更新时间」
|
||||||
|
1. **本周进展卡片「交付项目」→「进行中项目」**(`web/app.js` 卡片标题/说明/标题栏摘要;口径未变,仍是 本周更新 ∩ 进行中+交付完成)。
|
||||||
|
2. **清空早于 2026-09-18 的「更新时间」**:`UPDATE custom_projects SET update_date=NULL WHERE update_date IS NOT NULL AND update_date!='' AND update_date < '2026-09-18'` → 清空 **111** 行;保留 11 行(均为 2026-09-18 及以后编辑的,含 "YYYY-MM-DD HH:MM" 提交时间)。备份 `data/board.db.bak-before-clearing-updatedate-20260920_214254`。
|
||||||
|
- 影响:`update_date` 仅剩 11 行有值;「删除本周更新」现在只会命中这 11 行(都在本周)。后续「修改/新建」会按提交时间重新写入。
|
||||||
|
|
||||||
|
## 表格排序 + 计划筛选 + 列序/按钮调整
|
||||||
|
1. **列序**:`customTableHtml` 里把「备注」与「更新时间」**对调**(… 风险值 / **更新时间** / **备注** / 操作)。
|
||||||
|
2. **去掉「删除本周更新」按钮**:删除 `renderCustomList` 卡片头里的按钮、`bindCustomList` 里的绑定与整个 `purgeWeek()` 函数(后端 `/custom/weekly-preview`、`/custom/purge-week` 接口保留未删)。
|
||||||
|
3. **表头排序**:`下单时间`(sign_date) / `订单金额`(amount, 数值) / `更新时间`(update_date) 三个表头可点击排序,`state.customSort = {key, dir}`;点不同列默认「降序」,再点同列切换升/降;表头显示 ▲/▼,卡片副标题显示当前排序。
|
||||||
|
4. **新增「推动验收计划」筛选**:工具栏加 `#cAcceptPlan`(`state.customAcceptPlan`),选项取数据中实际出现过的值(Q3/Q4/27年/-- 等);导出也带 `accept_plan` 参数(后端 `export_custom` 新增 `accept_plan`)。
|
||||||
|
- `state` 新增 `customAcceptPlan` / `customSort`,均加入 `UI_FIELDS` 持久化。
|
||||||
|
- 后端 `export.py` 改动 → 重启 uvicorn(PID 23436);`node --check` / `ast.parse` 通过。实测导出 `accept_plan=Q3` 生效。**本轮未 push**。
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,7 @@ class CustomProject(Base, TimestampMixin):
|
||||||
source: Mapped[str] = mapped_column(String(16), default="import", nullable=False)
|
source: Mapped[str] = mapped_column(String(16), default="import", nullable=False)
|
||||||
|
|
||||||
# ---- 市场/销售侧台账字段(来源《定开项目.xlsx》,2026-09 起)----
|
# ---- 市场/销售侧台账字段(来源《定开项目.xlsx》,2026-09 起)----
|
||||||
origin: Mapped[str | None] = mapped_column(String(32), nullable=True) # 项目归属:新签订单/存量项目/汇智直签
|
origin: Mapped[str | None] = mapped_column(String(32), nullable=True) # 项目归属:华智下单/存量项目/汇智直签/提前交付
|
||||||
project_code: Mapped[str | None] = mapped_column(
|
project_code: Mapped[str | None] = mapped_column(
|
||||||
String(64), nullable=True, index=True
|
String(64), nullable=True, index=True
|
||||||
) # 项目ID(市场编号,导入时用于去重)
|
) # 项目ID(市场编号,导入时用于去重)
|
||||||
|
|
@ -426,7 +426,7 @@ class CustomProject(Base, TimestampMixin):
|
||||||
) # 市场责任人(销售侧;多人用 / 分隔,与花名册无关)
|
) # 市场责任人(销售侧;多人用 / 分隔,与花名册无关)
|
||||||
project_status: Mapped[str | None] = mapped_column(
|
project_status: Mapped[str | None] = mapped_column(
|
||||||
String(32), nullable=True
|
String(32), nullable=True
|
||||||
) # 项目状态:已验收/待开发/交付完成/交付中/退单
|
) # 项目状态:已验收/待开发/交付完成/进行中/退单
|
||||||
accept_plan: Mapped[str | None] = mapped_column(
|
accept_plan: Mapped[str | None] = mapped_column(
|
||||||
String(32), nullable=True
|
String(32), nullable=True
|
||||||
) # 推动验收计划(Q1..Q4 / 27年)
|
) # 推动验收计划(Q1..Q4 / 27年)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ def export_custom(
|
||||||
region: str | None = None,
|
region: str | None = None,
|
||||||
origin: str | None = None,
|
origin: str | None = None,
|
||||||
status: str | None = None,
|
status: str | None = None,
|
||||||
|
accept_plan: str | None = None,
|
||||||
keyword: str | None = None,
|
keyword: str | None = None,
|
||||||
archived: str = Query("false", pattern="^(true|false|all)$"),
|
archived: str = Query("false", pattern="^(true|false|all)$"),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|
@ -63,6 +64,8 @@ def export_custom(
|
||||||
q = q.filter(CustomProject.origin == origin)
|
q = q.filter(CustomProject.origin == origin)
|
||||||
if status:
|
if status:
|
||||||
q = q.filter(CustomProject.project_status == status)
|
q = q.filter(CustomProject.project_status == status)
|
||||||
|
if accept_plan:
|
||||||
|
q = q.filter(CustomProject.accept_plan == accept_plan)
|
||||||
if keyword:
|
if keyword:
|
||||||
q = q.filter(CustomProject.name.contains(keyword))
|
q = q.filter(CustomProject.name.contains(keyword))
|
||||||
projects = _sort_by_update(q.all())
|
projects = _sort_by_update(q.all())
|
||||||
|
|
|
||||||
BIN
data/board.db
BIN
data/board.db
Binary file not shown.
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
| 表格列 | 数据库列 | 说明 |
|
| 表格列 | 数据库列 | 说明 |
|
||||||
|--------------|-----------------|------|
|
|--------------|-----------------|------|
|
||||||
| 项目归属 | origin | 新签订单 / 存量项目 / 汇智直签 |
|
| 项目归属 | origin | 华智下单 / 存量项目 / 汇智直签 / 提前交付(「新签订单」自动映射为「华智下单」) |
|
||||||
| 项目名称 | name | 匹配键之一 |
|
| 项目名称 | name | 匹配键之一 |
|
||||||
| 项目ID | project_code | 匹配键(优先) |
|
| 项目ID | project_code | 匹配键(优先) |
|
||||||
| 下单时间 | sign_date | 日期取 YYYY-MM-DD,像「2024年3月前」这种文本原样保留 |
|
| 下单时间 | sign_date | 日期取 YYYY-MM-DD,像「2024年3月前」这种文本原样保留 |
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
| 市场下单人天 | man_days | |
|
| 市场下单人天 | man_days | |
|
||||||
| 订单金额 | amount | |
|
| 订单金额 | amount | |
|
||||||
| 市场责任人 | market_owner | 销售侧人员,与花名册无关,可「张宁/叶镇」这种多人写法 |
|
| 市场责任人 | market_owner | 销售侧人员,与花名册无关,可「张宁/叶镇」这种多人写法 |
|
||||||
| 项目状态 | project_status | 已验收 / 待开发 / 交付完成 / 交付中 / 退单 |
|
| 项目状态 | project_status | 已验收 / 待开发 / 交付完成 / 进行中 / 退单(「交付中」自动映射为「进行中」) |
|
||||||
| 验收时间 | accept_date | 存的是季度文本(Q1..Q4),不是日期 |
|
| 验收时间 | accept_date | 存的是季度文本(Q1..Q4),不是日期 |
|
||||||
| 推动验收计划 | accept_plan | Q1..Q4 / 27年 |
|
| 推动验收计划 | accept_plan | Q1..Q4 / 27年 |
|
||||||
| 风险值 | risk_level | 1.高风险 / 2.低风险 |
|
| 风险值 | risk_level | 1.高风险 / 2.低风险 |
|
||||||
|
|
@ -137,6 +137,11 @@ def read_rows(xlsx: str) -> list[dict]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
row["region"] = normalize_region(row.get("office"))
|
row["region"] = normalize_region(row.get("office"))
|
||||||
|
# 历史取值改名(2026-09-20):项目归属 新签订单 → 华智下单;项目状态 交付中 → 进行中
|
||||||
|
if row.get("origin") == "新签订单":
|
||||||
|
row["origin"] = "华智下单"
|
||||||
|
if row.get("project_status") == "交付中":
|
||||||
|
row["project_status"] = "进行中"
|
||||||
# 验收日期(真实日期,区别于 accept_date 季度文本):仅对「已验收」且验收时间为季度文本的项目,
|
# 验收日期(真实日期,区别于 accept_date 季度文本):仅对「已验收」且验收时间为季度文本的项目,
|
||||||
# 补一个当年季度末占位,使「本年/本月验收」统计有数可算。用户后续在界面上改了真日期不会被覆盖。
|
# 补一个当年季度末占位,使「本年/本月验收」统计有数可算。用户后续在界面上改了真日期不会被覆盖。
|
||||||
row["accept_date_real"] = None
|
row["accept_date_real"] = None
|
||||||
|
|
|
||||||
134
web/app.js
134
web/app.js
|
|
@ -31,6 +31,8 @@
|
||||||
customStage: "",
|
customStage: "",
|
||||||
customStatus: "",
|
customStatus: "",
|
||||||
customOrigin: "",
|
customOrigin: "",
|
||||||
|
customAcceptPlan: "",
|
||||||
|
customSort: null, // { key: amount | sign_date | update_date, dir: asc | desc }
|
||||||
customArchived: "false",
|
customArchived: "false",
|
||||||
customRows: [],
|
customRows: [],
|
||||||
intakeType: "general",
|
intakeType: "general",
|
||||||
|
|
@ -1472,12 +1474,12 @@
|
||||||
const CUSTOM_STATUS_COLOR = {
|
const CUSTOM_STATUS_COLOR = {
|
||||||
已验收: "#16a34a",
|
已验收: "#16a34a",
|
||||||
交付完成: "#0891b2",
|
交付完成: "#0891b2",
|
||||||
交付中: "#2563eb",
|
进行中: "#2563eb",
|
||||||
待开发: "#b45309",
|
待开发: "#b45309",
|
||||||
退单: "#7a8394",
|
退单: "#7a8394",
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 项目状态:已验收/交付完成/交付中/待开发/退单 */
|
/** 项目状态:已验收/交付完成/进行中/待开发/退单 */
|
||||||
function statusPill(code) {
|
function statusPill(code) {
|
||||||
if (!code) return `<span style="color:var(--muted)">—</span>`;
|
if (!code) return `<span style="color:var(--muted)">—</span>`;
|
||||||
const color = CUSTOM_STATUS_COLOR[code] || "#7a8394";
|
const color = CUSTOM_STATUS_COLOR[code] || "#7a8394";
|
||||||
|
|
@ -1496,8 +1498,8 @@
|
||||||
let keywordFocus = false;
|
let keywordFocus = false;
|
||||||
|
|
||||||
/** 定开「项目归属」取值:前三项视为「已下单」,提前交付为下单前状态 */
|
/** 定开「项目归属」取值:前三项视为「已下单」,提前交付为下单前状态 */
|
||||||
const CUSTOM_ORIGINS = ["新签订单", "存量项目", "汇智直签", "提前交付", "其他"];
|
const CUSTOM_ORIGINS = ["华智下单", "存量项目", "汇智直签", "提前交付"];
|
||||||
const CUSTOM_PLACED = ["新签订单", "存量项目", "汇智直签"];
|
const CUSTOM_PLACED = ["华智下单", "存量项目", "汇智直签"];
|
||||||
const isPlaced = (p) => CUSTOM_PLACED.indexOf(p.origin) >= 0;
|
const isPlaced = (p) => CUSTOM_PLACED.indexOf(p.origin) >= 0;
|
||||||
/** 年度计收目标(万元),来源《年度任务.xlsx》(Q1 35 / Q2 225 / Q3 160 / Q4 260,合计 680) */
|
/** 年度计收目标(万元),来源《年度任务.xlsx》(Q1 35 / Q2 225 / Q3 160 / Q4 260,合计 680) */
|
||||||
const CUSTOM_ANNUAL_TARGETS = { 1: 35, 2: 225, 3: 160, 4: 260 };
|
const CUSTOM_ANNUAL_TARGETS = { 1: 35, 2: 225, 3: 160, 4: 260 };
|
||||||
|
|
@ -1532,11 +1534,15 @@
|
||||||
`</optgroup>`;
|
`</optgroup>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_OPTIONS = ["已验收", "待开发", "交付完成", "交付中", "退单"];
|
const STATUS_OPTIONS = ["已验收", "待开发", "交付完成", "进行中", "退单"];
|
||||||
const statusFilterHtml = `<option value="">全部状态</option>` +
|
const statusFilterHtml = `<option value="">全部状态</option>` +
|
||||||
STATUS_OPTIONS.map((s) => `<option value="${esc(s)}" ${s === state.customStatus ? "selected" : ""}>${esc(s)}</option>`).join("");
|
STATUS_OPTIONS.map((s) => `<option value="${esc(s)}" ${s === state.customStatus ? "selected" : ""}>${esc(s)}</option>`).join("");
|
||||||
const originFilterHtml = `<option value="">全部归属</option>` +
|
const originFilterHtml = `<option value="">全部归属</option>` +
|
||||||
CUSTOM_ORIGINS.map((o) => `<option value="${esc(o)}" ${o === state.customOrigin ? "selected" : ""}>${esc(o)}</option>`).join("");
|
CUSTOM_ORIGINS.map((o) => `<option value="${esc(o)}" ${o === state.customOrigin ? "selected" : ""}>${esc(o)}</option>`).join("");
|
||||||
|
// 推动验收计划:取数据里实际出现过的值
|
||||||
|
const planVals = Array.from(new Set(allRows.map((p) => String(p.accept_plan || "").trim()).filter(Boolean))).sort();
|
||||||
|
const planFilterHtml = `<option value="">全部计划</option>` +
|
||||||
|
planVals.map((v) => `<option value="${esc(v)}" ${v === state.customAcceptPlan ? "selected" : ""}>${esc(v)}</option>`).join("");
|
||||||
|
|
||||||
const isList = state.customTab !== "weekly" && state.customTab !== "annual";
|
const isList = state.customTab !== "weekly" && state.customTab !== "annual";
|
||||||
const toolbar = `
|
const toolbar = `
|
||||||
|
|
@ -1550,6 +1556,7 @@
|
||||||
<select id="cOrigin" class="sel" style="width:130px">${originFilterHtml}</select>
|
<select id="cOrigin" class="sel" style="width:130px">${originFilterHtml}</select>
|
||||||
<select id="cRegion" class="sel" style="width:132px">${regionFilterHtml}</select>
|
<select id="cRegion" class="sel" style="width:132px">${regionFilterHtml}</select>
|
||||||
<select id="cStatus" class="sel" style="width:140px">${statusFilterHtml}</select>
|
<select id="cStatus" class="sel" style="width:140px">${statusFilterHtml}</select>
|
||||||
|
<select id="cAcceptPlan" class="sel" style="width:120px" title="推动验收计划">${planFilterHtml}</select>
|
||||||
<button class="btn sm" id="btnExportCustom">导出 Excel</button>
|
<button class="btn sm" id="btnExportCustom">导出 Excel</button>
|
||||||
<button class="btn sm primary" id="btnNewCustom">${Ico.svg("plus")}新建定开项目</button>
|
<button class="btn sm primary" id="btnNewCustom">${Ico.svg("plus")}新建定开项目</button>
|
||||||
<span class="period-hint">全部 ${allRows.length} 个项目</span>`
|
<span class="period-hint">全部 ${allRows.length} 个项目</span>`
|
||||||
|
|
@ -1572,6 +1579,8 @@
|
||||||
if (rs) rs.addEventListener("change", () => { state.customRegion = rs.value; renderCustom(); });
|
if (rs) rs.addEventListener("change", () => { state.customRegion = rs.value; renderCustom(); });
|
||||||
const st = $("#cStatus");
|
const st = $("#cStatus");
|
||||||
if (st) st.addEventListener("change", () => { state.customStatus = st.value; renderCustom(); });
|
if (st) st.addEventListener("change", () => { state.customStatus = st.value; renderCustom(); });
|
||||||
|
const ap = $("#cAcceptPlan");
|
||||||
|
if (ap) ap.addEventListener("change", () => { state.customAcceptPlan = ap.value; renderCustom(); });
|
||||||
if (isList) bindKeyword("cKeyword", { enter: true });
|
if (isList) bindKeyword("cKeyword", { enter: true });
|
||||||
const bn = $("#btnNewCustom");
|
const bn = $("#btnNewCustom");
|
||||||
if (bn) bn.addEventListener("click", () => openNewCustomDialog(() => afterWrite()));
|
if (bn) bn.addEventListener("click", () => openNewCustomDialog(() => afterWrite()));
|
||||||
|
|
@ -1582,6 +1591,7 @@
|
||||||
if (state.customOrigin) q.push("origin=" + encodeURIComponent(state.customOrigin));
|
if (state.customOrigin) q.push("origin=" + encodeURIComponent(state.customOrigin));
|
||||||
if (state.customRegion) q.push("region=" + encodeURIComponent(state.customRegion));
|
if (state.customRegion) q.push("region=" + encodeURIComponent(state.customRegion));
|
||||||
if (state.customStatus) q.push("status=" + encodeURIComponent(state.customStatus));
|
if (state.customStatus) q.push("status=" + encodeURIComponent(state.customStatus));
|
||||||
|
if (state.customAcceptPlan) q.push("accept_plan=" + encodeURIComponent(state.customAcceptPlan));
|
||||||
if (state.keyword) q.push("keyword=" + encodeURIComponent(state.keyword));
|
if (state.keyword) q.push("keyword=" + encodeURIComponent(state.keyword));
|
||||||
download("/api/export/custom?" + q.join("&"));
|
download("/api/export/custom?" + q.join("&"));
|
||||||
});
|
});
|
||||||
|
|
@ -1589,19 +1599,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCustomList(allRows) {
|
function renderCustomList(allRows) {
|
||||||
// 列表:按 关键字 + 归属 + 区域 + 状态 在本地筛选;并按「更新时间」倒序
|
// 列表:按 关键字 + 归属 + 区域 + 状态 + 推动验收计划 在本地筛选;再按表头排序
|
||||||
const kw = (state.keyword || "").trim();
|
const kw = (state.keyword || "").trim();
|
||||||
let list = allRows;
|
let list = allRows;
|
||||||
if (kw) list = list.filter((p) => String(p.name || "").includes(kw));
|
if (kw) list = list.filter((p) => String(p.name || "").includes(kw));
|
||||||
if (state.customOrigin) list = list.filter((p) => (p.origin || "") === state.customOrigin);
|
if (state.customOrigin) list = list.filter((p) => (p.origin || "") === state.customOrigin);
|
||||||
if (state.customRegion) list = list.filter((p) => (p.region || "") === state.customRegion);
|
if (state.customRegion) list = list.filter((p) => (p.region || "") === state.customRegion);
|
||||||
if (state.customStatus) list = list.filter((p) => (p.project_status || "") === state.customStatus);
|
if (state.customStatus) list = list.filter((p) => (p.project_status || "") === state.customStatus);
|
||||||
const view = list.slice().sort((a, b) =>
|
if (state.customAcceptPlan) list = list.filter((p) => String(p.accept_plan || "").trim() === state.customAcceptPlan);
|
||||||
String(b.update_date || "").localeCompare(String(a.update_date || ""))
|
const s = state.customSort || { key: "update_date", dir: "desc" };
|
||||||
);
|
const sv = (p, k) => (k === "amount" ? Number(p.amount || 0) : String(p[k] || ""));
|
||||||
|
const view = list.slice().sort((a, b) => {
|
||||||
|
const va = sv(a, s.key), vb = sv(b, s.key);
|
||||||
|
const c = va < vb ? -1 : va > vb ? 1 : 0;
|
||||||
|
return s.dir === "asc" ? c : -c;
|
||||||
|
});
|
||||||
|
|
||||||
// ---- 统计条:基于「全部已下单项目」,不随搜索 / 筛选变化 ----
|
// ---- 统计条:基于「全部已下单项目」,不随搜索 / 筛选变化 ----
|
||||||
const placed = allRows.filter(isPlaced); // 已下单 = 新签订单 / 存量项目 / 汇智直签
|
const placed = allRows.filter(isPlaced); // 已下单 = 华智下单 / 存量项目 / 汇智直签
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const Y = today.getFullYear();
|
const Y = today.getFullYear();
|
||||||
const M = today.getMonth() + 1;
|
const M = today.getMonth() + 1;
|
||||||
|
|
@ -1634,47 +1649,48 @@
|
||||||
${stat(`本季度验收(${Y}年Q${Q})`, accQuarter.length, sum(accQuarter, "amount"), "var(--amber)")}
|
${stat(`本季度验收(${Y}年Q${Q})`, accQuarter.length, sum(accQuarter, "amount"), "var(--amber)")}
|
||||||
${stat(`本年验收项目(${Y}年)`, accYear.length, sum(accYear, "amount"), "var(--amber)")}
|
${stat(`本年验收项目(${Y}年)`, accYear.length, sum(accYear, "amount"), "var(--amber)")}
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-note">「已下单项目 / 合同额」与「本季度·本年下单」只统计 <b>新签订单 / 存量项目 / 汇智直签</b>(提前交付不计入下单);统计基于全部项目,不受搜索 / 筛选影响。</div>`;
|
<div class="stat-note">「已下单项目 / 合同额」与「本季度·本年下单」只统计 <b>华智下单 / 存量项目 / 汇智直签</b>(提前交付不计入下单);统计基于全部项目,不受搜索 / 筛选影响。</div>`;
|
||||||
|
|
||||||
const manDays = view.reduce((a, p) => a + Number(p.man_days || 0), 0);
|
const manDays = view.reduce((a, p) => a + Number(p.man_days || 0), 0);
|
||||||
const highRisk = view.filter((p) => String(p.risk_level || "").includes("高")).length;
|
const highRisk = view.filter((p) => String(p.risk_level || "").includes("高")).length;
|
||||||
const accepted = view.filter((p) => p.project_status === "已验收").length;
|
const accepted = view.filter((p) => p.project_status === "已验收").length;
|
||||||
|
|
||||||
|
const sortLabel = { sign_date: "下单时间", amount: "订单金额", update_date: "更新时间" }[s.key] || "更新时间";
|
||||||
return `<div class="card">
|
return `<div class="card">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<h4>定开项目总览<span class="sub">按「更新时间」倒序 · 共 ${view.length} 个${state.customStatus ? "(" + esc(state.customStatus) + ")" : ""} · 列表只读,改数据请点「修改」</span></h4>
|
<h4>定开项目总览<span class="sub">按「${sortLabel}」${s.dir === "asc" ? "升序" : "降序"} · 共 ${view.length} 个${state.customStatus ? "(" + esc(state.customStatus) + ")" : ""} · 点表头(下单时间 / 订单金额 / 更新时间)可排序;列表只读,改数据请点「修改」</span></h4>
|
||||||
<span class="period-hint">人天 ${manDays.toLocaleString()} · 高风险 ${highRisk} 个 · 已验收 ${accepted} 个</span>
|
<span class="period-hint">人天 ${manDays.toLocaleString()} · 高风险 ${highRisk} 个 · 已验收 ${accepted} 个</span>
|
||||||
<div class="inline-fields">
|
|
||||||
<button class="btn sm danger" id="btnPurgeWeek" title="删除「更新时间」在本周(周一~周日)内的定开项目">删除本周更新</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
${statBar}
|
${statBar}
|
||||||
${view.length ? customTableHtml(view) : `<div class="empty">${state.keyword ? "没有匹配「" + esc(state.keyword) + "」的定开项目" : "当前筛选条件下没有定开项目"}</div>`}
|
${view.length ? customTableHtml(view) : `<div class="empty">${state.keyword ? "没有匹配「" + esc(state.keyword) + "」的定开项目" : "当前筛选条件下没有定开项目"}</div>`}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 定开项目表格(「项目明细」与「本周进展」两个 tab 共用) */
|
/** 定开项目表格(「项目总览」与「本周进展」两个 tab 共用);下单时间 / 订单金额 / 更新时间 表头可排序 */
|
||||||
function customTableHtml(view, withOps) {
|
function customTableHtml(view, withOps) {
|
||||||
const ops = withOps !== false;
|
const ops = withOps !== false;
|
||||||
|
const s = state.customSort || { key: "update_date", dir: "desc" };
|
||||||
|
const sth = (label, k, cls) =>
|
||||||
|
`<th class="${cls ? cls + " " : ""}sortable" data-sort="${k}" style="cursor:pointer;user-select:none" title="点击排序">${label}${s.key === k ? (s.dir === "asc" ? " ▲" : " ▼") : ""}</th>`;
|
||||||
return `<div class="tbl-wrap">
|
return `<div class="tbl-wrap">
|
||||||
<table class="tbl">
|
<table class="tbl">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th style="min-width:200px">项目名称</th>
|
<th style="min-width:200px">项目名称</th>
|
||||||
<th>项目ID</th>
|
<th>项目ID</th>
|
||||||
<th>下单时间</th>
|
${sth("下单时间", "sign_date")}
|
||||||
<th>项目归属</th>
|
<th>项目归属</th>
|
||||||
<th>办事处</th>
|
<th>办事处</th>
|
||||||
<th>行业</th>
|
<th>行业</th>
|
||||||
<th class="num">市场下单人天</th>
|
<th class="num">市场下单人天</th>
|
||||||
<th class="num">订单金额</th>
|
${sth("订单金额", "amount", "num")}
|
||||||
<th>市场责任人</th>
|
<th>市场责任人</th>
|
||||||
<th>项目状态</th>
|
<th>项目状态</th>
|
||||||
<th class="num">进度</th>
|
<th class="num">进度</th>
|
||||||
<th>验收时间</th>
|
<th>验收时间</th>
|
||||||
<th>推动验收计划</th>
|
<th>推动验收计划</th>
|
||||||
<th>风险值</th>
|
<th>风险值</th>
|
||||||
|
${sth("更新时间", "update_date")}
|
||||||
<th>备注</th>
|
<th>备注</th>
|
||||||
<th>更新时间</th>
|
|
||||||
${ops ? `<th class="col-ops">操作</th>` : ""}
|
${ops ? `<th class="col-ops">操作</th>` : ""}
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -1696,12 +1712,12 @@
|
||||||
<td class="num">${money(p.amount)}</td>
|
<td class="num">${money(p.amount)}</td>
|
||||||
<td class="nowrap">${esc(p.market_owner || "—")}</td>
|
<td class="nowrap">${esc(p.market_owner || "—")}</td>
|
||||||
<td class="nowrap">${statusPill(p.project_status)}</td>
|
<td class="nowrap">${statusPill(p.project_status)}</td>
|
||||||
<td class="num">${(p.project_status === "交付中" || Number(p.progress || 0) > 0) ? Math.round(p.progress || 0) + "%" : "—"}</td>
|
<td class="num">${(p.project_status === "进行中" || Number(p.progress || 0) > 0) ? Math.round(p.progress || 0) + "%" : "—"}</td>
|
||||||
<td class="nowrap">${p.accept_date ? esc(p.accept_date) : "—"}</td>
|
<td class="nowrap">${p.accept_date ? esc(p.accept_date) : "—"}</td>
|
||||||
<td>${esc(p.accept_plan || "—")}</td>
|
<td>${esc(p.accept_plan || "—")}</td>
|
||||||
<td class="nowrap">${riskPill(p.risk_level)}</td>
|
<td class="nowrap">${riskPill(p.risk_level)}</td>
|
||||||
<td>${esc((p.remark || "").slice(0, 40)) || "—"}</td>
|
|
||||||
<td class="nowrap">${esc(p.update_date || "—")}</td>
|
<td class="nowrap">${esc(p.update_date || "—")}</td>
|
||||||
|
<td>${esc((p.remark || "").slice(0, 40)) || "—"}</td>
|
||||||
${ops ? `<td class="nowrap col-ops"><button class="btn sm" data-cedit="${p.id}">修改</button></td>` : ""}
|
${ops ? `<td class="nowrap col-ops"><button class="btn sm" data-cedit="${p.id}">修改</button></td>` : ""}
|
||||||
</tr>`).join("")}
|
</tr>`).join("")}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -1723,25 +1739,25 @@
|
||||||
|
|
||||||
const sum = (arr, f) => arr.reduce((a, p) => a + Number(p[f] || 0), 0);
|
const sum = (arr, f) => arr.reduce((a, p) => a + Number(p[f] || 0), 0);
|
||||||
const acc = week.filter((p) => p.project_status === "已验收");
|
const acc = week.filter((p) => p.project_status === "已验收");
|
||||||
// 本周新增项目:按「下单时间」在本周的新下单 / 提前交付项目(不是「本周更新」)
|
// 新增项目:按「下单时间」在本周的新下单 / 提前进行中项目(不是「本周更新」)
|
||||||
const newDeal = allRows.filter((p) => inWeek(p.sign_date) && (isPlaced(p) || p.origin === "提前交付"));
|
const newDeal = allRows.filter((p) => inWeek(p.sign_date) && (isPlaced(p) || p.origin === "提前交付"));
|
||||||
// 交付项目:本周更新的「交付中 + 交付完成」
|
// 进行中项目:本周更新的「进行中 + 交付完成」
|
||||||
const doing = week.filter((p) => p.project_status === "交付中" || p.project_status === "交付完成");
|
const doing = week.filter((p) => p.project_status === "进行中" || p.project_status === "交付完成");
|
||||||
|
|
||||||
const stat = (label, rows, color) =>
|
const stat = (label, rows, color) =>
|
||||||
`<div class="stat" style="--c:${color}"><div class="stat-num">${rows.length}<span class="stat-unit">个</span> · <span class="stat-money">${fen(sum(rows, "amount"))}</span><span class="stat-unit">万</span></div><div class="stat-label">${label}</div></div>`;
|
`<div class="stat" style="--c:${color}"><div class="stat-num">${rows.length}<span class="stat-unit">个</span> · <span class="stat-money">${fen(sum(rows, "amount"))}</span><span class="stat-unit">万</span></div><div class="stat-label">${label}</div></div>`;
|
||||||
|
|
||||||
const statBar = `<div class="stat-row">
|
const statBar = `<div class="stat-row">
|
||||||
${stat("验收情况", acc, "var(--amber)")}
|
${stat("验收情况", acc, "var(--amber)")}
|
||||||
${stat("本周新增项目", newDeal, "var(--violet)")}
|
${stat("新增项目", newDeal, "var(--violet)")}
|
||||||
${stat("交付项目", doing, "var(--primary)")}
|
${stat("进行中项目", doing, "var(--primary)")}
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-note">本周 = <b>${start} ~ ${end}</b>;<b>本周新增项目</b> = 按「下单时间」在本周的新下单 / 提前交付项目(已下单 + 提前交付);验收情况 / 交付项目 为「更新时间」在本周的项目(交付项目 = 交付中 + 交付完成)。</div>`;
|
<div class="stat-note">本周 = <b>${start} ~ ${end}</b>;<b>新增项目</b> = 按「下单时间」在本周的新下单 / 提前进行中项目(已下单 + 提前交付);验收情况 / 进行中项目 为「更新时间」在本周的项目(进行中项目 = 进行中 + 交付完成)。</div>`;
|
||||||
|
|
||||||
return `<div class="card">
|
return `<div class="card">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<h4>本周进展<span class="sub">更新时间在本周(${start} ~ ${end})· 共 ${week.length} 个项目 · 列表只读,改数据请点项目名称</span></h4>
|
<h4>本周进展<span class="sub">更新时间在本周(${start} ~ ${end})· 共 ${week.length} 个项目 · 列表只读,改数据请点项目名称</span></h4>
|
||||||
<span class="period-hint">验收 ${acc.length} · 新增 ${newDeal.length} · 交付 ${doing.length}</span>
|
<span class="period-hint">验收 ${acc.length} · 新增 ${newDeal.length} · 进行中 ${doing.length}</span>
|
||||||
</div>
|
</div>
|
||||||
${statBar}
|
${statBar}
|
||||||
${week.length
|
${week.length
|
||||||
|
|
@ -1808,11 +1824,11 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-note">计收项目 = 按「验收时间」归属季度的项目;新增签单 = 按「下单时间」归属本年度季度、且项目归属为已下单(新签订单 / 存量项目 / 汇智直签)的项目;金额由「元」换算为「万元」;计收完成率 = 计收项目金额 ÷ 计收目标。</div>
|
<div class="stat-note">计收项目 = 按「验收时间」归属季度的项目;新增签单 = 按「下单时间」归属本年度季度、且项目归属为已下单(华智下单 / 存量项目 / 汇智直签)的项目;金额由「元」换算为「万元」;计收完成率 = 计收项目金额 ÷ 计收目标。</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 项目明细表:列表只读,行内只有「修改」;名称可点开详情 */
|
/** 项目总览表:列表只读,行内只有「修改」;名称可点开详情;表头(下单时间 / 订单金额 / 更新时间)可排序 */
|
||||||
function bindCustomList(host) {
|
function bindCustomList(host) {
|
||||||
host.querySelectorAll("[data-copen]").forEach((a) =>
|
host.querySelectorAll("[data-copen]").forEach((a) =>
|
||||||
a.addEventListener("click", () => openCustomDrawer(Number(a.dataset.copen), "view"))
|
a.addEventListener("click", () => openCustomDrawer(Number(a.dataset.copen), "view"))
|
||||||
|
|
@ -1820,44 +1836,14 @@
|
||||||
host.querySelectorAll("[data-cedit]").forEach((b) =>
|
host.querySelectorAll("[data-cedit]").forEach((b) =>
|
||||||
b.addEventListener("click", () => openCustomDrawer(Number(b.dataset.cedit), "edit"))
|
b.addEventListener("click", () => openCustomDrawer(Number(b.dataset.cedit), "edit"))
|
||||||
);
|
);
|
||||||
const bp = $("#btnPurgeWeek");
|
host.querySelectorAll("[data-sort]").forEach((th) =>
|
||||||
if (bp) bp.addEventListener("click", purgeWeek);
|
th.addEventListener("click", () => {
|
||||||
}
|
const k = th.dataset.sort;
|
||||||
|
const cur = state.customSort || { key: "update_date", dir: "desc" };
|
||||||
/** 删除本周(周一~周日)更新的定开项目:先预览数量,二次确认后执行。 */
|
state.customSort = { key: k, dir: cur.key === k && cur.dir === "desc" ? "asc" : "desc" };
|
||||||
async function purgeWeek() {
|
renderCustom();
|
||||||
let pv;
|
})
|
||||||
try {
|
);
|
||||||
pv = await api("/custom/weekly-preview");
|
|
||||||
} catch (e) {
|
|
||||||
return toast("读取本周更新失败:" + e.message);
|
|
||||||
}
|
|
||||||
if (!pv.count) {
|
|
||||||
return toast("本周(" + pv.start + " ~ " + pv.end + ")没有更新的定开项目");
|
|
||||||
}
|
|
||||||
const ok = await confirmDialog({
|
|
||||||
title: "删除本周更新的定开项目",
|
|
||||||
okText: "确认删除 " + pv.count + " 个",
|
|
||||||
danger: true,
|
|
||||||
requireText: "删除",
|
|
||||||
requirePlaceholder: "输入「删除」以确认",
|
|
||||||
body:
|
|
||||||
`<p style="margin:0 0 10px">将删除<b>更新时间在本周(${esc(pv.start)} ~ ${esc(pv.end)})</b>内的定开项目:` +
|
|
||||||
`<b>${pv.count} 个</b>,合同额合计 <b>${fen(pv.amount)} 万</b>。</p>` +
|
|
||||||
`<div class="notice warn">此操作会连同项目的更新记录一起删除,且<b>不可撤销</b>。` +
|
|
||||||
`若只是想标记,请在详情里改状态。</div>` +
|
|
||||||
(pv.names && pv.names.length
|
|
||||||
? `<div class="notice info" style="max-height:160px;overflow:auto">${pv.names.map((n) => esc(n)).join("、")}${pv.count > pv.names.length ? " …" : ""}</div>`
|
|
||||||
: ""),
|
|
||||||
});
|
|
||||||
if (!ok) return;
|
|
||||||
try {
|
|
||||||
const r = await post("/custom/purge-week", {});
|
|
||||||
toast("已删除本周更新的 " + r.deleted + " 个定开项目");
|
|
||||||
afterWrite();
|
|
||||||
} catch (e) {
|
|
||||||
toast("删除失败:" + e.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1873,9 +1859,9 @@
|
||||||
// 选项直接对齐《定开项目.xlsx》台账字段取值
|
// 选项直接对齐《定开项目.xlsx》台账字段取值
|
||||||
const opts = (list, sel) =>
|
const opts = (list, sel) =>
|
||||||
list.map((v) => `<option value="${esc(v)}" ${v === sel ? "selected" : ""}>${esc(v)}</option>`).join("");
|
list.map((v) => `<option value="${esc(v)}" ${v === sel ? "selected" : ""}>${esc(v)}</option>`).join("");
|
||||||
const originOpts = opts(CUSTOM_ORIGINS, "新签订单");
|
const originOpts = opts(CUSTOM_ORIGINS, "华智下单");
|
||||||
const industryOpts = opts(["安平", "政府", "企业", "教育", "交通", "医疗", "高速", "行业", "其他"], "");
|
const industryOpts = opts(["安平", "政府", "企业", "教育", "交通", "医疗", "高速", "行业", "其他"], "");
|
||||||
const statusOpts = opts(["待开发", "交付中", "交付完成", "已验收", "退单"], "待开发");
|
const statusOpts = opts(["待开发", "进行中", "交付完成", "已验收", "退单"], "待开发");
|
||||||
const riskOpts = opts(["1.高风险", "2.低风险"], "2.低风险");
|
const riskOpts = opts(["1.高风险", "2.低风险"], "2.低风险");
|
||||||
const numVal = (sel) => ($(sel).value === "" ? null : Number($(sel).value));
|
const numVal = (sel) => ($(sel).value === "" ? null : Number($(sel).value));
|
||||||
const txtVal = (sel) => $(sel).value.trim() || null;
|
const txtVal = (sel) => $(sel).value.trim() || null;
|
||||||
|
|
@ -1895,7 +1881,7 @@
|
||||||
<div class="form-row"><label>订单金额(元)</label><input class="inp" id="ncAmount" type="number" min="0" style="width:100%" /></div>
|
<div class="form-row"><label>订单金额(元)</label><input class="inp" id="ncAmount" type="number" min="0" style="width:100%" /></div>
|
||||||
<div class="form-row"><label>市场责任人</label><input class="inp" id="ncMarketOwner" placeholder="多人用 / 分隔" style="width:100%" /></div>
|
<div class="form-row"><label>市场责任人</label><input class="inp" id="ncMarketOwner" placeholder="多人用 / 分隔" style="width:100%" /></div>
|
||||||
<div class="form-row"><label>项目状态</label><select class="sel" id="ncStatus" style="width:100%">${statusOpts}</select></div>
|
<div class="form-row"><label>项目状态</label><select class="sel" id="ncStatus" style="width:100%">${statusOpts}</select></div>
|
||||||
<div class="form-row"><label>进度(%)</label><input class="inp" id="ncProgress" type="number" min="0" max="100" step="5" placeholder="交付中项目填 0~100" style="width:100%" /></div>
|
<div class="form-row"><label>进度(%)</label><input class="inp" id="ncProgress" type="number" min="0" max="100" step="5" placeholder="进行中项目填 0~100" style="width:100%" /></div>
|
||||||
<div class="form-row"><label>验收时间</label><input class="inp" id="ncAcceptQuarter" placeholder="季度文本,如 Q3" style="width:100%" /></div>
|
<div class="form-row"><label>验收时间</label><input class="inp" id="ncAcceptQuarter" placeholder="季度文本,如 Q3" style="width:100%" /></div>
|
||||||
<div class="form-row"><label>推动验收计划</label><input class="inp" id="ncAcceptPlan" placeholder="如 Q4 / 27年" style="width:100%" /></div>
|
<div class="form-row"><label>推动验收计划</label><input class="inp" id="ncAcceptPlan" placeholder="如 Q4 / 27年" style="width:100%" /></div>
|
||||||
<div class="form-row"><label>风险值</label><select class="sel" id="ncRisk" style="width:100%">${riskOpts}</select></div>
|
<div class="form-row"><label>风险值</label><select class="sel" id="ncRisk" style="width:100%">${riskOpts}</select></div>
|
||||||
|
|
@ -2030,7 +2016,7 @@
|
||||||
const optList = (list, sel) => list.map((v) => `<option value="${esc(v)}" ${v === sel ? "selected" : ""}>${esc(v)}</option>`).join("");
|
const optList = (list, sel) => list.map((v) => `<option value="${esc(v)}" ${v === sel ? "selected" : ""}>${esc(v)}</option>`).join("");
|
||||||
const originOpts = optList(CUSTOM_ORIGINS, p.origin);
|
const originOpts = optList(CUSTOM_ORIGINS, p.origin);
|
||||||
const industryOpts = optList(["安平", "政府", "企业", "教育", "交通", "医疗", "高速", "行业", "其他"], p.industry);
|
const industryOpts = optList(["安平", "政府", "企业", "教育", "交通", "医疗", "高速", "行业", "其他"], p.industry);
|
||||||
const statusOpts = optList(["待开发", "交付中", "交付完成", "已验收", "退单"], p.project_status);
|
const statusOpts = optList(["待开发", "进行中", "交付完成", "已验收", "退单"], p.project_status);
|
||||||
const riskOpts = optList(["1.高风险", "2.低风险"], p.risk_level);
|
const riskOpts = optList(["1.高风险", "2.低风险"], p.risk_level);
|
||||||
|
|
||||||
// 市场台账(来源《定开项目.xlsx》):详情只读,修改模式给输入框
|
// 市场台账(来源《定开项目.xlsx》):详情只读,修改模式给输入框
|
||||||
|
|
@ -2065,7 +2051,7 @@
|
||||||
${ro("市场下单人天", numOrDash(p.man_days))}
|
${ro("市场下单人天", numOrDash(p.man_days))}
|
||||||
${ro("市场责任人", esc(p.market_owner || "—"))}
|
${ro("市场责任人", esc(p.market_owner || "—"))}
|
||||||
${ro("项目状态", statusPill(p.project_status))}
|
${ro("项目状态", statusPill(p.project_status))}
|
||||||
${ro("进度", (p.project_status === "交付中" || Number(p.progress || 0) > 0) ? Math.round(p.progress || 0) + "%" : "—")}
|
${ro("进度", (p.project_status === "进行中" || Number(p.progress || 0) > 0) ? Math.round(p.progress || 0) + "%" : "—")}
|
||||||
${ro("验收时间", esc(p.accept_date || "—"))}
|
${ro("验收时间", esc(p.accept_date || "—"))}
|
||||||
${ro("推动验收计划", esc(p.accept_plan || "—"))}
|
${ro("推动验收计划", esc(p.accept_plan || "—"))}
|
||||||
${ro("风险值", riskPill(p.risk_level))}
|
${ro("风险值", riskPill(p.risk_level))}
|
||||||
|
|
@ -3342,7 +3328,7 @@
|
||||||
const UI_KEY = "unis.ui.v1";
|
const UI_KEY = "unis.ui.v1";
|
||||||
const UI_FIELDS = ["view", "gran", "weekId", "monthIdx", "categoryId", "keyOnly", "keyword",
|
const UI_FIELDS = ["view", "gran", "weekId", "monthIdx", "categoryId", "keyOnly", "keyword",
|
||||||
"projOwner", "projIdleOnly", "showArchived", "focusFilter", "focusArchived",
|
"projOwner", "projIdleOnly", "showArchived", "focusFilter", "focusArchived",
|
||||||
"customTab", "customArchived", "customRegion", "customOrigin", "staffArchived", "staffGroup"];
|
"customTab", "customArchived", "customRegion", "customOrigin", "customAcceptPlan", "customSort", "staffArchived", "staffGroup"];
|
||||||
|
|
||||||
function saveUI() {
|
function saveUI() {
|
||||||
const o = {};
|
const o = {};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue