Merge pull request '给用户增加“备注”列' (#46) from dev_jacy into dev
CI / verify (pull_request) Waiting to run

Reviewed-on: https://git.yqidian.com/jacy/dukang/pulls/46
This commit was merged in pull request #46.
This commit is contained in:
2026-08-25 10:10:07 +08:00
13 changed files with 66 additions and 44 deletions
+1
View File
@@ -169,6 +169,7 @@ export type AdminUserRow = {
wxOpenId: string | null; wxOpenId: string | null;
wechatVerified: boolean; wechatVerified: boolean;
nickname: string | null; nickname: string | null;
hqRemark: string | null;
status: number; status: number;
sourceType: string; sourceType: string;
sourceRefId: string | null; sourceRefId: string | null;
+28 -21
View File
@@ -31,40 +31,40 @@ type UserOrderRow = {
createdAt: string; createdAt: string;
}; };
function NicknameCell({ function HqRemarkCell({
id, id,
nickname, hqRemark,
onSaved, onSaved,
}: { }: {
id: string; id: string;
nickname: string | null; hqRemark: string | null;
onSaved: (id: string, nickname: string | null) => void; onSaved: (id: string, hqRemark: string | null) => void;
}) { }) {
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [value, setValue] = useState(nickname ?? ''); const [value, setValue] = useState(hqRemark ?? '');
const savingRef = useRef(false); const savingRef = useRef(false);
useEffect(() => { useEffect(() => {
if (!editing) setValue(nickname ?? ''); if (!editing) setValue(hqRemark ?? '');
}, [nickname, editing]); }, [hqRemark, editing]);
async function commit() { async function commit() {
if (savingRef.current) return; if (savingRef.current) return;
const next = value.trim() || null; const next = value.trim() || null;
const prev = nickname?.trim() || null; const prev = hqRemark?.trim() || null;
setEditing(false); setEditing(false);
if (next === prev) return; if (next === prev) return;
savingRef.current = true; savingRef.current = true;
try { try {
const res = await request<{ id: string; nickname: string | null }>(`/admin/users/${id}`, { const res = await request<{ id: string; hqRemark: string | null }>(`/admin/users/${id}`, {
method: 'PUT', method: 'PUT',
body: JSON.stringify({ nickname: next ?? '' }), body: JSON.stringify({ hqRemark: next ?? '' }),
}); });
onSaved(id, res.nickname); onSaved(id, res.hqRemark);
message.success('昵称已保存'); message.success('备注已保存');
} catch (e) { } catch (e) {
message.error(e instanceof Error ? e.message : '保存失败'); message.error(e instanceof Error ? e.message : '保存失败');
setValue(nickname ?? ''); setValue(hqRemark ?? '');
} finally { } finally {
savingRef.current = false; savingRef.current = false;
} }
@@ -75,7 +75,7 @@ function NicknameCell({
<Input <Input
size="small" size="small"
autoFocus autoFocus
maxLength={64} maxLength={128}
value={value} value={value}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
onBlur={() => void commit()} onBlur={() => void commit()}
@@ -90,14 +90,14 @@ function NicknameCell({
return ( return (
<span <span
title="双击修改,离开后保存" title="双击修改备注,离开后保存"
onDoubleClick={(e) => { onDoubleClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setEditing(true); setEditing(true);
}} }}
style={{ cursor: 'text', display: 'inline-block', minWidth: 48 }} style={{ cursor: 'text', display: 'inline-block', minWidth: 48 }}
> >
{nickname || '—'} {hqRemark || '—'}
</span> </span>
); );
} }
@@ -332,17 +332,23 @@ export default function UsersPage() {
title: '昵称', title: '昵称',
dataIndex: 'nickname', dataIndex: 'nickname',
width: 140, width: 140,
render: (v: string | null) => v || '—',
},
{
title: '备注',
dataIndex: 'hqRemark',
width: 160,
render: (v: string | null, row) => ( render: (v: string | null, row) => (
<NicknameCell <HqRemarkCell
id={row.id} id={row.id}
nickname={v} hqRemark={v}
onSaved={(id, nickname) => { onSaved={(id, hqRemark) => {
setData((prev) => setData((prev) =>
prev prev
? { ...prev, items: prev.items.map((u) => (u.id === id ? { ...u, nickname } : u)) } ? { ...prev, items: prev.items.map((u) => (u.id === id ? { ...u, hqRemark } : u)) }
: prev, : prev,
); );
setDetail((d) => (d && d.id === id ? { ...d, nickname } : d)); setDetail((d) => (d && d.id === id ? { ...d, hqRemark } : d));
}} }}
/> />
), ),
@@ -558,6 +564,7 @@ export default function UsersPage() {
<Descriptions.Item label="ID">{detail.id}</Descriptions.Item> <Descriptions.Item label="ID">{detail.id}</Descriptions.Item>
<Descriptions.Item label="用户编号">{detail.userNo}</Descriptions.Item> <Descriptions.Item label="用户编号">{detail.userNo}</Descriptions.Item>
<Descriptions.Item label="昵称">{detail.nickname || '—'}</Descriptions.Item> <Descriptions.Item label="昵称">{detail.nickname || '—'}</Descriptions.Item>
<Descriptions.Item label="备注">{detail.hqRemark || '—'}</Descriptions.Item>
<Descriptions.Item label="手机号">{maskPhone(detail.phone)}</Descriptions.Item> <Descriptions.Item label="手机号">{maskPhone(detail.phone)}</Descriptions.Item>
<Descriptions.Item label="验手机时间"> <Descriptions.Item label="验手机时间">
{detail.phoneVerifiedAt ? new Date(detail.phoneVerifiedAt).toLocaleString('zh-CN') : '未验证'} {detail.phoneVerifiedAt ? new Date(detail.phoneVerifiedAt).toLocaleString('zh-CN') : '未验证'}
+2 -2
View File
@@ -28,7 +28,7 @@
| 3.5.6 | 08-23 | HQ 任务导出;核销/财务银行信息;工单图片入任务;运营去调试;门店审核全屏 | [`v3.5.6`](./杜康好客-v3.5.6-开发文档.md) | | 3.5.6 | 08-23 | HQ 任务导出;核销/财务银行信息;工单图片入任务;运营去调试;门店审核全屏 | [`v3.5.6`](./杜康好客-v3.5.6-开发文档.md) |
| 3.5.7 | 08-23 | 门店端核销记录时间显示秒;首页「今日核销金额」文案 | [`v3.5.7`](./杜康好客-v3.5.7-开发文档.md) | | 3.5.7 | 08-23 | 门店端核销记录时间显示秒;首页「今日核销金额」文案 | [`v3.5.7`](./杜康好客-v3.5.7-开发文档.md) |
| 3.5.8 | 08-24 | HQ 单账号追加/撤销权限;运营改客服修复;城市门店服务+城市范围 | [`v3.5.8`](./杜康好客-v3.5.8-开发文档.md) | | 3.5.8 | 08-24 | HQ 单账号追加/撤销权限;运营改客服修复;城市门店服务+城市范围 | [`v3.5.8`](./杜康好客-v3.5.8-开发文档.md) |
| 3.5.9 | 08-25 | 门店累计核销好客权益;HQ 表格去省略号;序号列 + 列设置;用户列表改昵称 / 手机号不脱敏 | [`v3.5.9`](./杜康好客-v3.5.9-开发文档.md) | | 3.5.9 | 08-25 | 门店累计核销好客权益;HQ 表格去省略号;序号列 + 列设置;用户备注 / 手机号不脱敏 | [`v3.5.9`](./杜康好客-v3.5.9-开发文档.md) |
--- ---
@@ -139,7 +139,7 @@
- HQ 门店列表:店名完整展示、操作列右固定、表过宽可横向滚动(v3.5.9 去掉省略号)。 - HQ 门店列表:店名完整展示、操作列右固定、表过宽可横向滚动(v3.5.9 去掉省略号)。
- HQ 主列表:最左序号;「列设置」控制显示列与顺序,偏好挂当前 HQ 账号。 - HQ 主列表:最左序号;「列设置」控制显示列与顺序,偏好挂当前 HQ 账号。
- 门店列表展示「累计核销好客权益」(该店核销券面额合计)。 - 门店列表展示「累计核销好客权益」(该店核销券面额合计)。
- HQ 用户列表:双击昵称、离开编辑即保存;列表手机号不脱敏(详情仍脱敏)。 - HQ 用户列表:昵称只读;双击「备注」离开即保存(`hq_remark`;列表手机号不脱敏(详情仍脱敏)。
- 详 [`v3.4.16`](./杜康好客-v3.4.16-门店联系电话与体验优化.md)、[`v3.5.9`](./杜康好客-v3.5.9-开发文档.md)。 - 详 [`v3.4.16`](./杜康好客-v3.4.16-门店联系电话与体验优化.md)、[`v3.5.9`](./杜康好客-v3.5.9-开发文档.md)。
--- ---
+1 -1
View File
@@ -67,7 +67,7 @@
| 日期 | 说明 | | 日期 | 说明 |
|------|------| |------|------|
| 2026-08-25 | v3.5.9:门店列表累计核销好客权益;HQ 表格去省略号;主列表序号 + 列设置存 HQ 账号;用户列表双击改昵称、手机号不脱敏 | | 2026-08-25 | v3.5.9:门店列表累计核销好客权益;HQ 表格去省略号;主列表序号 + 列设置存 HQ 账号;用户列表备注(不改昵称、手机号不脱敏 |
| 2026-08-24 | v3.5.8HQ 单账号追加/撤销权限;运营改客服;城市门店服务与城市范围;分类删除权限与概览按权限/城市裁剪 | | 2026-08-24 | v3.5.8HQ 单账号追加/撤销权限;运营改客服;城市门店服务与城市范围;分类删除权限与概览按权限/城市裁剪 |
| 2026-08-23 | v3.5.7:门店核销记录时间显示秒;首页「今日到账金额」→「今日核销金额」 | | 2026-08-23 | v3.5.7:门店核销记录时间显示秒;首页「今日到账金额」→「今日核销金额」 |
| 2026-08-23 | v3.5.6:开发计划任务导出;核销/财务银行信息与打款凭证;工单图片入任务;运营去调试;门店审核全屏 | | 2026-08-23 | v3.5.6:开发计划任务导出;核销/财务银行信息与打款凭证;工单图片入任务;运营去调试;门店审核全屏 |
+16 -10
View File
@@ -1,7 +1,7 @@
# 杜康好客 · v3.5.9 开发文档 # 杜康好客 · v3.5.9 开发文档
> **2026-08-25** · admin-web / API > **2026-08-25** · admin-web / API
> **主题**:门店累计核销好客权益、HQ 表格去省略号、序号列与列设置;用户列表改昵称 / 手机号不脱敏 > **主题**:门店累计核销好客权益、HQ 表格去省略号、序号列与列设置;用户备注 / 手机号不脱敏
--- ---
@@ -10,7 +10,7 @@
1. **门店列表**增加「累计核销好客权益」:该店历史核销券面额合计。 1. **门店列表**增加「累计核销好客权益」:该店历史核销券面额合计。
2. **全 HQ 表格**去掉 `...` 截断,长文本完整展示,横向滚动。 2. **全 HQ 表格**去掉 `...` 截断,长文本完整展示,横向滚动。
3. 每张 **主列表**:最左序号(跨页连续);「列设置」弹窗控制显示列与顺序,偏好存当前 HQ 账号。 3. 每张 **主列表**:最左序号(跨页连续);「列设置」弹窗控制显示列与顺序,偏好存当前 HQ 账号。
4. **用户列表**双击昵称进入编辑,离开编辑(失焦 / 回车 / Esc)即写入数据库;手机号列表明文,不脱敏。 4. **用户列表**昵称只读(用户自己的);新增「备注」列,双击编辑 HQ 标记,离开即写入 `user_user.hq_remark`;手机号列表明文,不脱敏。
**不做**:C 端 / 合伙人 / 门店端;详情 Drawer 内嵌表不加列设置。 **不做**:C 端 / 合伙人 / 门店端;详情 Drawer 内嵌表不加列设置。
@@ -52,20 +52,21 @@
|------|------|------| |------|------|------|
| GET | `/admin/auth/me` | `listColumnPrefs` | | GET | `/admin/auth/me` | `listColumnPrefs` |
| PUT | `/admin/me/list-columns/:listKey` | `{ order, hidden }``{ reset: true }`;只改当前账号 | | PUT | `/admin/me/list-columns/:listKey` | `{ order, hidden }``{ reset: true }`;只改当前账号 |
| PUT | `/admin/users/:id` | `{ nickname }`,空串存 `null` | | PUT | `/admin/users/:id` | `{ hqRemark }`,空串存 `null`;不改 nickname |
`listKey` 白名单见 `packages/shared-types/src/hq-list-columns.ts`。无新权限键。 `listKey` 白名单见 `packages/shared-types/src/hq-list-columns.ts`。无新权限键。
--- ---
## 5. 用户列表:改昵称 / 手机号不脱敏 ## 5. 用户列表:备注 / 手机号不脱敏
| 项 | 行为 | | 项 | 行为 |
|----|------| |----|------|
| 昵称 | 双击单元格进入输入;失焦、回车或 Esc 退出编辑即 `PUT /admin/users/:id` 保存(空串写成 `null`);最长 64 | | 昵称 | 只读,来自用户自己的 `nickname`HQ 不可改 |
| 备注 | 双击单元格进入输入;失焦、回车或 Esc 退出编辑即 `PUT /admin/users/:id` `{ hqRemark }`(空串写成 `null`);最长 128C 端不展示 |
| 手机号 | 列表列明文 `phone`,不再 `maskPhone`。详情抽屉仍脱敏 | | 手机号 | 列表列明文 `phone`,不再 `maskPhone`。详情抽屉仍脱敏 |
审计:`USER_UPDATE`(修改用户昵称)。无新权限键,已登录 HQ 即可(与用户列表同入口)。 审计:`USER_UPDATE`(修改用户备注)。无新权限键,已登录 HQ 即可(与用户列表同入口)。
--- ---
@@ -73,7 +74,12 @@
`hq_account.list_column_prefs` JSON NULL。 `hq_account.list_column_prefs` JSON NULL。
脚本:[`server/dukang-api/prisma/migrate-hq-list-column-prefs-v359.sql`](../server/dukang-api/prisma/migrate-hq-list-column-prefs-v359.sql) `user_user.hq_remark` VARCHAR(128) NULL。
脚本:
- [`migrate-hq-list-column-prefs-v359.sql`](../server/dukang-api/prisma/migrate-hq-list-column-prefs-v359.sql)
- [`migrate-user-hq-remark-v359.sql`](../server/dukang-api/prisma/migrate-user-hq-remark-v359.sql)
--- ---
@@ -81,7 +87,7 @@
| 步骤 | 动作 | | 步骤 | 动作 |
|------|------| |------|------|
| 1 | 执行 `migrate-hq-list-column-prefs-v359.sql``prisma db push` | | 1 | 执行 `migrate-hq-list-column-prefs-v359.sql``migrate-user-hq-remark-v359.sql``prisma db push` |
| 2 | `pnpm build`shared-types + API + admin-web | | 2 | `pnpm build`shared-types + API + admin-web |
| 3 | 发 `dukang-api` + `admin-web` | | 3 | 发 `dukang-api` + `admin-web` |
@@ -97,7 +103,7 @@
| 表格 hook | `apps/admin-web/src/lib/useAdminListColumns.tsx` | | 表格 hook | `apps/admin-web/src/lib/useAdminListColumns.tsx` |
| 弹窗 | `AdminListColumnsModal.tsx` | | 弹窗 | `AdminListColumnsModal.tsx` |
| 样式 | `admin.css` | | 样式 | `admin.css` |
| 改昵称 | `admin-users.service.ts` · `UsersPage.tsx` | | 备注 | `admin-users.service.ts` · `UsersPage.tsx` |
--- ---
@@ -109,5 +115,5 @@
- [ ] 列设置可隐藏/排序;保存后刷新仍在;重置恢复默认 - [ ] 列设置可隐藏/排序;保存后刷新仍在;重置恢复默认
- [ ] 序号、操作列不能在弹窗关掉或拖走 - [ ] 序号、操作列不能在弹窗关掉或拖走
- [ ] 详情描述列表仍可省略 - [ ] 详情描述列表仍可省略
- [ ] 用户列表双击昵称可改,离开编辑后库中已更新 - [ ] 用户列表昵称只读;双击「备注」可改,离开编辑后 `user_user.hq_remark` 已更新;C 端看不到该字段
- [ ] 用户列表手机号完整可见(详情仍脱敏) - [ ] 用户列表手机号完整可见(详情仍脱敏)
+1 -1
View File
@@ -43,7 +43,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
**HQ 权限(v3.5.8**:生效 =(角色 ∪ 追加)− 撤销。城市范围绑在账号上(空=全国;「城市门店服务」必须勾城)。门店 API 按 `cityIds` 强制过滤。城市门店服务可新增分类、不可删除;概览按权限与城市范围裁剪。 **HQ 权限(v3.5.8**:生效 =(角色 ∪ 追加)− 撤销。城市范围绑在账号上(空=全国;「城市门店服务」必须勾城)。门店 API 按 `cityIds` 强制过滤。城市门店服务可新增分类、不可删除;概览按权限与城市范围裁剪。
**HQ 列表(v3.5.9**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)存 `hq_account.list_column_prefs`。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表双击昵称、离开即保存;列表手机号不脱敏。 **HQ 列表(v3.5.9**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)存 `hq_account.list_column_prefs`。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表昵称只读;双击「备注」离开即保存(`hq_remark`;列表手机号不脱敏。
## 5. 验收用例(必过) ## 5. 验收用例(必过)
+1 -1
View File
@@ -88,7 +88,7 @@
| 开城 | 城市、合伙人(全城/区域+佣金)、仓库 | | 开城 | 城市、合伙人(全城/区域+佣金)、仓库 |
| 商品 | SKU、上下架、详情模板 | | 商品 | SKU、上下架、详情模板 |
| 门店 | 审核、套餐 Tab 直存/审核、合伙人列、累计核销好客权益 | | 门店 | 审核、套餐 Tab 直存/审核、合伙人列、累计核销好客权益 |
| 用户 | 列表手机号明文;双击昵称离开即保存 | | 用户 | 列表手机号明文;备注双击离开即保存(不改昵称) |
| 交易 | 订单、权益券、核销记录、推广码+metrics | | 交易 | 订单、权益券、核销记录、推广码+metrics |
| 财务 | 门店/合伙人/酒厂/物流账单;打款确认 | | 财务 | 门店/合伙人/酒厂/物流账单;打款确认 |
| 工单 | 售后四类型 + 技术支持(ST) + 开发计划 | | 工单 | 售后四类型 + 技术支持(ST) + 开发计划 |
+1 -1
View File
@@ -4,7 +4,7 @@ export interface AdminListQuery {
} }
export interface UpdateAdminUserRequest { export interface UpdateAdminUserRequest {
nickname: string; hqRemark: string;
} }
export interface AdminPageResult<T> { export interface AdminPageResult<T> {
@@ -0,0 +1,3 @@
-- v3.5.9 补丁:HQ 管理员对用户的备注(不改 nickname)
ALTER TABLE `user_user`
ADD COLUMN `hq_remark` VARCHAR(128) NULL AFTER `nickname`;
+2
View File
@@ -1309,6 +1309,8 @@ model User {
wxOpenId String? @map("wx_open_id") @db.VarChar(64) wxOpenId String? @map("wx_open_id") @db.VarChar(64)
wxUnionId String? @map("wx_union_id") @db.VarChar(64) wxUnionId String? @map("wx_union_id") @db.VarChar(64)
nickname String? @db.VarChar(64) nickname String? @db.VarChar(64)
/// HQ 管理员备注(C 端不展示、不可改)
hqRemark String? @map("hq_remark") @db.VarChar(128)
avatarResourceId BigInt? @map("avatar_resource_id") @db.UnsignedBigInt avatarResourceId BigInt? @map("avatar_resource_id") @db.UnsignedBigInt
status Int @default(1) @db.TinyInt status Int @default(1) @db.TinyInt
/// 测试账号:命中全局测试白名单手机号 /// 测试账号:命中全局测试白名单手机号
@@ -142,7 +142,7 @@ export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = {
[HqOperationAction.HQ_ACCOUNT_CREATE]: '新增 HQ 管理员', [HqOperationAction.HQ_ACCOUNT_CREATE]: '新增 HQ 管理员',
[HqOperationAction.HQ_ACCOUNT_UPDATE]: '编辑 HQ 管理员', [HqOperationAction.HQ_ACCOUNT_UPDATE]: '编辑 HQ 管理员',
[HqOperationAction.HQ_PERMISSION_UPDATE]: '配置 HQ 权限', [HqOperationAction.HQ_PERMISSION_UPDATE]: '配置 HQ 权限',
[HqOperationAction.USER_UPDATE]: '修改用户昵称', [HqOperationAction.USER_UPDATE]: '修改用户备注',
[HqOperationAction.USER_DELETE]: '删除用户', [HqOperationAction.USER_DELETE]: '删除用户',
[HqOperationAction.USER_BATCH_DELETE]: '批量删除用户', [HqOperationAction.USER_BATCH_DELETE]: '批量删除用户',
[HqOperationAction.ORDER_SHIP]: '订单发货', [HqOperationAction.ORDER_SHIP]: '订单发货',
@@ -37,6 +37,7 @@ function mapAdminUserRow(u: {
mergedIntoUserId: bigint | null; mergedIntoUserId: bigint | null;
wxOpenId: string | null; wxOpenId: string | null;
nickname: string | null; nickname: string | null;
hqRemark: string | null;
status: number; status: number;
sourceType: string; sourceType: string;
sourceRefId: bigint | null; sourceRefId: bigint | null;
@@ -56,6 +57,7 @@ function mapAdminUserRow(u: {
wxOpenId: u.wxOpenId, wxOpenId: u.wxOpenId,
wechatVerified: !!u.wxOpenId, wechatVerified: !!u.wxOpenId,
nickname: u.nickname, nickname: u.nickname,
hqRemark: u.hqRemark,
status: u.status, status: u.status,
sourceType: u.sourceType, sourceType: u.sourceType,
sourceRefId: u.sourceRefId, sourceRefId: u.sourceRefId,
@@ -100,6 +102,7 @@ export class AdminUsersService {
mergedIntoUserId: true, mergedIntoUserId: true,
wxOpenId: true, wxOpenId: true,
nickname: true, nickname: true,
hqRemark: true,
status: true, status: true,
sourceType: true, sourceType: true,
sourceRefId: true, sourceRefId: true,
@@ -188,14 +191,14 @@ export class AdminUsersService {
}); });
} }
async updateUser(id: bigint, dto: { nickname: string }) { async updateUser(id: bigint, dto: { hqRemark: string }) {
const user = await this.prisma.user.findUnique({ where: { id }, select: { id: true } }); const user = await this.prisma.user.findUnique({ where: { id }, select: { id: true } });
if (!user) throw new NotFoundException('用户不存在'); if (!user) throw new NotFoundException('用户不存在');
const nickname = dto.nickname.trim() || null; const hqRemark = dto.hqRemark.trim() || null;
const updated = await this.prisma.user.update({ const updated = await this.prisma.user.update({
where: { id }, where: { id },
data: { nickname }, data: { hqRemark },
select: { id: true, userNo: true, nickname: true }, select: { id: true, userNo: true, hqRemark: true },
}); });
return serializeBigInt(updated); return serializeBigInt(updated);
} }
@@ -951,8 +951,8 @@ export class BatchDeleteUsersConfirmDto extends BatchDeleteUsersDto {
export class UpdateAdminUserDto { export class UpdateAdminUserDto {
@IsString() @IsString()
@MaxLength(64) @MaxLength(128)
nickname: string; hqRemark: string;
} }
/** HQ 订单发货(目前仅小飞侠 XFX) */ /** HQ 订单发货(目前仅小飞侠 XFX) */