diff --git a/apps/admin-web/src/pages/StoresPage.tsx b/apps/admin-web/src/pages/StoresPage.tsx index cbddec2..fa378e3 100644 --- a/apps/admin-web/src/pages/StoresPage.tsx +++ b/apps/admin-web/src/pages/StoresPage.tsx @@ -368,12 +368,18 @@ export default function StoresPage() { account?.phone || (typeof d.phone === 'string' ? d.phone : undefined); const storePhone = typeof d.phone === 'string' ? d.phone : undefined; + const contactPhone = + (typeof d.contactPhone === 'string' && d.contactPhone.trim()) || + storePhone || + loginPhone || + ''; const phoneMismatchNow = !!(loginPhone && storePhone && loginPhone !== storePhone); setPhoneMismatch(phoneMismatchNow ? String(loginPhone) : null); editForm.setFieldsValue({ name: d.name, - // 以门店手机号为准保存;若与账号登录号不一致,保存时会强制同步到登录账号 - phone: storePhone || loginPhone, + // 登录手机号(老板);与 StoreAccount 同步 + phone: loginPhone || storePhone, + contactPhone, intro: d.intro, benefitUsageRule: d.benefitUsageRule != null && @@ -431,6 +437,7 @@ export default function StoresPage() { const payload = { name: v.name, phone: v.phone, + contactPhone: String(v.contactPhone || '').trim() || v.phone, coverUrl: v.coverUrl ?? '', envPhotoUrls: Array.isArray(v.envPhotoUrls) ? v.envPhotoUrls.map((u: string) => String(u || '').trim()).filter(Boolean) @@ -608,6 +615,7 @@ export default function StoresPage() { city: values.city, name: values.name.trim(), phone: values.phone.trim(), + contactPhone: String(values.contactPhone || values.phone || '').trim(), district: values.district.trim(), address: values.address.trim(), ...(values.latitude != null && @@ -673,7 +681,13 @@ export default function StoresPage() { render: (_, row) => row.category?.name || '—', }, { title: '城市', dataIndex: 'cityName', width: 80 }, - { title: '电话', dataIndex: 'phone', width: 120 }, + { title: '登录号', dataIndex: 'phone', width: 120 }, + { + title: '联系电话', + dataIndex: 'contactPhone', + width: 120, + render: (v: string | null | undefined, row) => v || row.phone, + }, { title: '营业状态', dataIndex: 'status', width: 90, render: (s) => {STORE_STATUS_LABELS[s] || s}, @@ -884,18 +898,26 @@ export default function StoresPage() { type="warning" showIcon style={{ marginBottom: 16 }} - message={`登录账号手机号仍为 ${phoneMismatch},与门店手机号不一致。请点击右上角「保存修改」同步,否则门店端无法用新号登录。`} + message={`主账号登录号为 ${phoneMismatch},与门店登录字段不一致。保存「登录手机号」将同步到门店端登录账号。`} /> ) : null} + + + - - + + + + + diff --git a/apps/h5-partner/src/lib/storeDraft.ts b/apps/h5-partner/src/lib/storeDraft.ts index 4786d42..c82c1b4 100644 --- a/apps/h5-partner/src/lib/storeDraft.ts +++ b/apps/h5-partner/src/lib/storeDraft.ts @@ -5,7 +5,10 @@ export type StoreDraftForm = { city: string; district: string; name: string; + /** 门店登录手机号(老板主账号) */ phone: string; + /** 对外联系电话(店长);可与登录号不同 */ + contactPhone: string; address: string; /** 门店坐标(定位或地理编码) */ latitude: string; @@ -51,6 +54,7 @@ export const defaultStoreForm = (): StoreDraftForm => ({ cityId: '', name: '', phone: '', + contactPhone: '', address: '', latitude: '', longitude: '', @@ -102,6 +106,9 @@ export function normalizeStoreDraftForm(raw: Partial | null | un ...raw, regionCodes: Array.isArray(raw.regionCodes) ? raw.regionCodes.map(String) : base.regionCodes, cityId: String(raw.cityId ?? base.cityId), + phone: String(raw.phone ?? base.phone), + contactPhone: + String(raw.contactPhone ?? '').trim() || String(raw.phone ?? '').trim() || base.contactPhone, latitude: raw.latitude != null && raw.latitude !== '' ? String(raw.latitude) : base.latitude, longitude: raw.longitude != null && raw.longitude !== '' ? String(raw.longitude) : base.longitude, openTime: String(raw.openTime ?? base.openTime), @@ -236,14 +243,16 @@ export function patchEnvPhotoAt(urls: string[], index: number, url: string): str export function validateStoreStep3( form: Pick< StoreDraftForm, - 'bankAccountName' | 'bankAccountNo' | 'bankBranch' | 'phone' + 'bankAccountName' | 'bankAccountNo' | 'bankBranch' | 'phone' | 'contactPhone' >, ): string | null { if (!form.bankAccountName.trim()) return '请填写户主姓名'; if (!form.bankAccountNo.trim()) return '请填写银行卡号'; if (!BANK_RE.test(form.bankAccountNo.replace(/\s/g, ''))) return '银行卡号须为 16~19 位数字'; if (!form.bankBranch.trim()) return '请填写开户支行'; - if (!form.phone.trim()) return '请填写联系电话'; - if (!PHONE_RE.test(form.phone.trim())) return '联系电话须为11位手机号'; + if (!form.phone.trim()) return '请填写门店登录手机号'; + if (!PHONE_RE.test(form.phone.trim())) return '门店登录手机号须为11位手机号'; + if (!form.contactPhone.trim()) return '请填写联系电话'; + if (!PHONE_RE.test(form.contactPhone.trim())) return '联系电话须为11位手机号'; return null; } diff --git a/apps/h5-partner/src/pages/StoreCreatePage.tsx b/apps/h5-partner/src/pages/StoreCreatePage.tsx index 280c311..940a03b 100644 --- a/apps/h5-partner/src/pages/StoreCreatePage.tsx +++ b/apps/h5-partner/src/pages/StoreCreatePage.tsx @@ -56,6 +56,7 @@ type StoreCategoryNode = { type FieldErrors = { phone?: string; + contactPhone?: string; }; @@ -237,9 +238,17 @@ export default function StoreCreatePage() { setSubmitError(''); - if ('phone' in patch) { + if ('phone' in patch || 'contactPhone' in patch) { - setFieldErrors((prev) => ({ ...prev, phone: undefined })); + setFieldErrors((prev) => ({ + + ...prev, + + ...('phone' in patch ? { phone: undefined } : {}), + + ...('contactPhone' in patch ? { contactPhone: undefined } : {}), + + })); } @@ -315,7 +324,9 @@ export default function StoreCreatePage() { const msg = validateStoreStep3(form); if (msg) { if (isPhoneValidationMessage(msg)) { - setFieldErrors({ phone: msg }); + setFieldErrors( + msg.includes('联系电话') ? { contactPhone: msg } : { phone: msg }, + ); reportStepError(msg); return; } @@ -336,7 +347,9 @@ export default function StoreCreatePage() { if (msg) { if (isPhoneValidationMessage(msg)) { - setFieldErrors({ phone: msg }); + setFieldErrors( + msg.includes('联系电话') ? { contactPhone: msg } : { phone: msg }, + ); return; } reportFormError(msg); @@ -441,6 +454,8 @@ export default function StoreCreatePage() { phone: form.phone.trim(), + contactPhone: form.contactPhone.trim() || form.phone.trim(), + district: form.district.trim(), address: form.address.trim(), @@ -1089,7 +1104,43 @@ export default function StoreCreatePage() {

- 该手机号将作为门店端登录账号。 + 老板手机号,作为门店端主账号登录凭证。 + +

+ + + +
+ + + +
+ + phone_in_talk + + patchForm({ contactPhone: e.target.value })} + + /> + +
+ + {fieldErrors.contactPhone && ( + +

{fieldErrors.contactPhone}

+ + )} + +

+ + 用户端门店详情展示与拨号使用此号码,可与登录号不同。

diff --git a/apps/h5-partner/src/pages/StoreDetailPage.tsx b/apps/h5-partner/src/pages/StoreDetailPage.tsx index e3fe98d..a7b7059 100644 --- a/apps/h5-partner/src/pages/StoreDetailPage.tsx +++ b/apps/h5-partner/src/pages/StoreDetailPage.tsx @@ -43,7 +43,8 @@ export default function StoreDetailPage() { const [loadError, setLoadError] = useState(''); const [form, setForm] = useState({ name: '', - phone: '', + loginPhone: '', + contactPhone: '', address: '', intro: '', benefitUsageRule: '', @@ -64,7 +65,8 @@ export default function StoreDetailPage() { setStore(data); setForm({ name: String(data.name || ''), - phone: String(data.phone || ''), + loginPhone: String(data.phone || ''), + contactPhone: String(data.contactPhone || data.phone || ''), address: String(data.address || ''), intro: String(data.intro || ''), benefitUsageRule: (() => { @@ -164,7 +166,7 @@ export default function StoreDetailPage() { method: 'PUT', body: JSON.stringify({ name: form.name.trim(), - phone: form.phone.trim(), + contactPhone: form.contactPhone.trim(), address: form.address.trim(), intro: form.intro.trim(), benefitUsageRule: form.benefitUsageRule.trim() || null, @@ -346,12 +348,30 @@ export default function StoreDetailPage() { setForm({ ...form, name: e.target.value })} style={{ width: '100%', padding: '12px 16px', border: '1px solid rgba(226,190,188,0.5)', borderRadius: 8, fontSize: 16, fontWeight: 500 }} />
+
+ +
+ lock + +
+

+ 老板主账号,用于门店端登录;如需变更请联系总部。 +

+
- call - setForm({ ...form, phone: e.target.value })} /> + phone_in_talk + setForm({ ...form, contactPhone: e.target.value })} + />
+

+ 店长或对外展示号码,用户端拨号使用此号码。 +

diff --git a/server/dukang-api/prisma/schema.prisma b/server/dukang-api/prisma/schema.prisma index 7f49349..505a7f3 100644 --- a/server/dukang-api/prisma/schema.prisma +++ b/server/dukang-api/prisma/schema.prisma @@ -1248,7 +1248,10 @@ model Store { partnerAccountId BigInt @map("partner_account_id") @db.UnsignedBigInt categoryId BigInt? @map("category_id") @db.UnsignedBigInt name String @db.VarChar(128) + /// 门店主账号登录手机号(老板);与 StoreAccount.phone 对齐 phone String @db.VarChar(20) + /// 对外联系电话(店长等);C 端拨号展示用;空则回退 phone + contactPhone String? @map("contact_phone") @db.VarChar(20) province String @db.VarChar(32) cityName String @map("city_name") @db.VarChar(32) district String @db.VarChar(32) diff --git a/server/dukang-api/src/common/compat/v31-compat.ts b/server/dukang-api/src/common/compat/v31-compat.ts index 4ca26ab..ec7c7fb 100644 --- a/server/dukang-api/src/common/compat/v31-compat.ts +++ b/server/dukang-api/src/common/compat/v31-compat.ts @@ -44,11 +44,36 @@ export function mapOrderCompat(store: T) { - return { +/** 对外联系电话;未单独配置时回退登录手机号 */ +export function resolveStoreContactPhone(store: { + phone?: string | null; + contactPhone?: string | null; +}): string { + const contact = String(store.contactPhone ?? '').trim(); + if (contact) return contact; + return String(store.phone ?? '').trim(); +} + +export function mapStoreCompat< + T extends { + coverResource?: { url?: string } | null; + phone?: string | null; + contactPhone?: string | null; + }, +>(store: T, opts?: { /** C 端:phone 字段对外可拨打号码 */ publicDial?: boolean }) { + const contactPhone = resolveStoreContactPhone(store) || null; + const mapped = { ...store, coverUrl: store.coverResource?.url ?? null, + contactPhone, }; + if (opts?.publicDial) { + return { + ...mapped, + phone: contactPhone || store.phone || null, + }; + } + return mapped; } export function mapStatusLogCompat(events: CommonEvent[]) { diff --git a/server/dukang-api/src/modules/ops/admin-stores.service.ts b/server/dukang-api/src/modules/ops/admin-stores.service.ts index 0f7351e..d3d066e 100644 --- a/server/dukang-api/src/modules/ops/admin-stores.service.ts +++ b/server/dukang-api/src/modules/ops/admin-stores.service.ts @@ -146,8 +146,10 @@ export class AdminStoresService { visibilityPhones: visibilityPhones.map((p) => p.phone), partner: store.partnerAccount, account: store.bindings[0]?.storeAccount ?? null, - /** 门店端登录手机号(store_account.phone),与 store.phone 应对齐 */ + /** 门店端登录手机号(store_account.phone);与 store.phone 对齐 */ loginPhone: store.bindings[0]?.storeAccount?.phone ?? store.phone, + /** 对外联系电话;空则回退登录号 */ + contactPhone: store.contactPhone?.trim() || store.phone, bindings: undefined, media, audits, @@ -275,11 +277,19 @@ export class AdminStoresService { if (dto.phone !== undefined) { const normalizedPhone = dto.phone.trim(); if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) { - throw new BadRequestException('请输入正确的手机号码'); + throw new BadRequestException('请输入正确的登录手机号'); + } + } + if (dto.contactPhone !== undefined) { + const contact = dto.contactPhone.trim(); + if (contact && !/^1[3-9]\d{9}$/.test(contact)) { + throw new BadRequestException('请输入正确的联系电话'); } } const normalizedPhone = dto.phone !== undefined ? dto.phone.trim() : undefined; + const normalizedContactPhone = + dto.contactPhone !== undefined ? dto.contactPhone.trim() || null : undefined; if (dto.visibilityWhitelistEnabled !== undefined || dto.visibilityPhones !== undefined) { const nextEnabled = dto.visibilityWhitelistEnabled !== undefined @@ -296,7 +306,7 @@ export class AdminStoresService { const needAccountSync = normalizedPhone !== undefined || dto.name !== undefined || bankTouched; - // 登录凭证在 store_account.phone;必须与门店展示手机号同步 + // 登录凭证在 store_account.phone;与 store.phone(老板登录号)同步,不与对外联系电话绑定 let primaryBinding: { storeAccount: { id: bigint; phone: string; name: string } | null; } | null = null; @@ -346,6 +356,9 @@ export class AdminStoresService { data: { ...(dto.name !== undefined ? { name: dto.name.trim() } : {}), ...(normalizedPhone !== undefined ? { phone: normalizedPhone } : {}), + ...(normalizedContactPhone !== undefined + ? { contactPhone: normalizedContactPhone } + : {}), ...(dto.intro !== undefined ? { intro: dto.intro } : {}), ...(dto.benefitUsageRule !== undefined ? { benefitUsageRule: normalizeStoreOptionalText(dto.benefitUsageRule) } @@ -562,6 +575,11 @@ export class AdminStoresService { ? !!dto.isTest : await this.testWhitelist.isPhoneInWhitelist(normalizedPhone); + const contactPhoneRaw = dto.contactPhone?.trim() || normalizedPhone; + if (!/^1[3-9]\d{9}$/.test(contactPhoneRaw)) { + throw new BadRequestException('请输入正确的联系电话'); + } + const store = await this.prisma.store.create({ data: { cityId: city.id, @@ -570,6 +588,7 @@ export class AdminStoresService { categoryId, name: dto.name, phone: normalizedPhone, + contactPhone: contactPhoneRaw, province: dto.province ?? city.province, cityName: dto.city ?? city.name, district: dto.district ?? '', diff --git a/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts b/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts index 9174629..2ab73f9 100644 --- a/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts +++ b/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts @@ -38,6 +38,11 @@ export class CreateStoreDto { @IsNotEmpty() phone: string; + /** 对外联系电话(店长);不传则默认与登录手机号相同 */ + @IsOptional() + @IsString() + contactPhone?: string; + @IsString() @IsNotEmpty() categoryId: string; @@ -162,6 +167,11 @@ export class UpdateStoreDto { @IsString() phone?: string; + /** 对外联系电话(店长) */ + @IsOptional() + @IsString() + contactPhone?: string; + @IsOptional() @IsString() intro?: string; diff --git a/server/dukang-api/src/modules/store/store.service.ts b/server/dukang-api/src/modules/store/store.service.ts index a5036db..e6511b4 100644 --- a/server/dukang-api/src/modules/store/store.service.ts +++ b/server/dukang-api/src/modules/store/store.service.ts @@ -179,11 +179,14 @@ export class StoreService { for (const store of visible) { const coords = await this.ensureStoreCoordinates(store); const { visibilityWhitelistEnabled: _wl, ...rest } = store; - const mapped = mapStoreCompat({ - ...rest, - latitude: coords?.latitude ?? store.latitude, - longitude: coords?.longitude ?? store.longitude, - }); + const mapped = mapStoreCompat( + { + ...rest, + latitude: coords?.latitude ?? store.latitude, + longitude: coords?.longitude ?? store.longitude, + }, + { publicDial: true }, + ); const distanceMeters = hasUser && coords ? Math.round(haversineMeters(userLat!, userLng!, coords.latitude, coords.longitude)) @@ -230,29 +233,32 @@ export class StoreService { }); const { visibilityWhitelistEnabled: _wl, ...rest } = store; return serializeBigInt( - mapStoreCompat({ - ...rest, - latitude: coords?.latitude ?? store.latitude, - longitude: coords?.longitude ?? store.longitude, - media, - packages: packageRows.map((p) => { - const imageUrls = normalizeStorePackageImageUrls({ - imageUrl: p.imageUrl, - imageUrls: p.imageUrls, - }); - return { - name: p.name, - price: p.price.toFixed(2), - dishes: p.dishes, - usableTime: p.usableTime, - otherNotes: p.otherNotes, - imageUrl: imageUrls[0] ?? null, - /** C 端始终返回数组,便于多图轮播 */ - imageUrls, - sortOrder: p.sortOrder, - }; - }), - }), + mapStoreCompat( + { + ...rest, + latitude: coords?.latitude ?? store.latitude, + longitude: coords?.longitude ?? store.longitude, + media, + packages: packageRows.map((p) => { + const imageUrls = normalizeStorePackageImageUrls({ + imageUrl: p.imageUrl, + imageUrls: p.imageUrls, + }); + return { + name: p.name, + price: p.price.toFixed(2), + dishes: p.dishes, + usableTime: p.usableTime, + otherNotes: p.otherNotes, + imageUrl: imageUrls[0] ?? null, + /** C 端始终返回数组,便于多图轮播 */ + imageUrls, + sortOrder: p.sortOrder, + }; + }), + }, + { publicDial: true }, + ), ); } @@ -275,7 +281,7 @@ export class StoreService { include: { category: true, coverResource: true }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], }); - return serializeBigInt(stores.map(mapStoreCompat)); + return serializeBigInt(stores.map((s) => mapStoreCompat(s))); } async partnerGetStore(partnerAccountId: bigint, storeId: bigint) { @@ -307,10 +313,10 @@ export class StoreService { async partnerCheckStorePhone(phone: string) { const normalizedPhone = phone.trim(); if (!normalizedPhone) { - return { available: false, message: '请填写联系电话' }; + return { available: false, message: '请填写门店登录手机号' }; } if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) { - return { available: false, message: '联系电话须为11位手机号' }; + return { available: false, message: '门店登录手机号须为11位手机号' }; } const existingAccount = await this.prisma.storeAccount.findUnique({ where: { phone: normalizedPhone }, @@ -360,6 +366,14 @@ export class StoreService { const confirmBindExisting = body.confirmBindExisting === true || body.confirmBindExisting === 'true'; await this.assertStorePhoneAvailable(normalizedPhone, confirmBindExisting); + const contactPhoneRaw = + body.contactPhone != null && String(body.contactPhone).trim() + ? String(body.contactPhone).trim() + : normalizedPhone; + if (!/^1[3-9]\d{9}$/.test(contactPhoneRaw)) { + throw new BadRequestException('联系电话须为11位手机号'); + } + const city = await this.resolvePartnerCity(partnerAccountId, body.cityId); const coverUrl = body.coverUrl ? String(body.coverUrl).trim() : ''; const envPhotoUrls = this.normalizeEnvPhotoUrls(body.envPhotoUrls); @@ -416,6 +430,7 @@ export class StoreService { categoryId, name: String(body.name), phone: normalizedPhone, + contactPhone: contactPhoneRaw, province: String(body.province ?? city.province ?? '河南省'), cityName: String(body.city ?? city.name ?? '郑州市'), district: String(body.district ?? ''), @@ -614,7 +629,13 @@ export class StoreService { } const name = body.name !== undefined ? String(body.name).trim() : undefined; - const phone = body.phone !== undefined ? String(body.phone).trim() : undefined; + // 合伙人资料修改:联系电话 → contactPhone(兼容旧客户端把 phone 当联系电话提交) + const contactPhone = + body.contactPhone !== undefined + ? String(body.contactPhone).trim() + : body.phone !== undefined + ? String(body.phone).trim() + : undefined; const address = body.address !== undefined ? String(body.address).trim() : undefined; const introRaw = body.intro !== undefined ? String(body.intro).trim() : undefined; const benefitUsageRuleRaw = @@ -627,7 +648,7 @@ export class StoreService { body.longitude !== undefined ? parseOptionalCoord(body.longitude, 'lng') : undefined; if (name !== undefined && !name) throw new BadRequestException('请填写门店名称'); - if (phone !== undefined && !/^1\d{10}$/.test(phone)) { + if (contactPhone !== undefined && !/^1[3-9]\d{9}$/.test(contactPhone)) { throw new BadRequestException('联系电话须为11位手机号'); } if (address !== undefined && !address) throw new BadRequestException('请填写详细地址'); @@ -650,7 +671,7 @@ export class StoreService { where: { id: storeId }, data: { ...(name !== undefined ? { name } : {}), - ...(phone !== undefined ? { phone } : {}), + ...(contactPhone !== undefined ? { contactPhone } : {}), ...(address !== undefined ? hasCoordsUpdate ? { address } diff --git a/杜康好客-v3-PRD.md b/杜康好客-v3-PRD.md index 0040fe6..2f72baa 100644 --- a/杜康好客-v3-PRD.md +++ b/杜康好客-v3-PRD.md @@ -21,6 +21,7 @@ | 3.4.13 | 08-05 | metrics/核销详情/版本联动 PUBLISHED/mini 体验/H5 OAuth | [`v3.4.13`](./杜康好客-v3.4.13-体验优化开发文档.md) | | 3.4.14 | 08-06 | mini-user 门头/套餐详情;小程序可配置;**统一测试白名单(不计账+限测可见+Mock旁路)** | [`v3.4.14`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) | | 3.4.15 | 08-07 | mini-user 门店列表卡片;HQ 门店照片替换/删除;套餐多图上限 20 | [`v3.4.15`](./杜康好客-v3.4.15-mini-user门店列表优化.md) | +| 3.4.16 | 08-09 | 门店登录手机号与对外联系电话分离 | 见 §3.12 | --- @@ -93,6 +94,17 @@ | 3.9 | 门店套餐 ≤10 条、独立审核 | v3.4.10 | | 3.10 | 开发计划/任务/版本/技术支持联动 | v3.4.11 | | 3.11 | 企微智能机器人 + 消息推送 Webhook | v3.4.11 | +| 3.12 | **登录手机号 ≠ 对外联系电话** | 见下 | + +### 3.12 门店登录号与对外联系电话 + +| 字段 | 含义 | 用途 | +|------|------|------| +| `phone` / 门店登录手机号 | 老板主账号 | 门店端短信登录;`StoreAccount.phone` | +| `contactPhone` / 联系电话 | 店长等对外号码 | C 端门店详情拨号与展示 | + +- 合伙人拓店入驻、门店资料修改均须采集 **联系电话**(可与登录号不同)。 +- 未填联系电话时,对外展示回退登录手机号(兼容旧数据)。 --- diff --git a/杜康好客-v3-现状对照.md b/杜康好客-v3-现状对照.md index 001b030..0708380 100644 --- a/杜康好客-v3-现状对照.md +++ b/杜康好客-v3-现状对照.md @@ -1,4 +1,4 @@ -# 杜康好客 · V3.0 现状对照 +# 杜康好客 · V3.0 现状对照 > 基准:仅 [`杜康好客-v3-PRD.md`](./杜康好客-v3-PRD.md) · 总部 = **`admin-web`**(非 H5) > 图例:✅ 完成 · 🔶 部分 · ❌ 未做 · ⚠️ 曾冲突已修 @@ -9,7 +9,7 @@ |------|------| | 主链路 | 登录→下单支付→权益→扫码核销→payout→HQ 打款 **可跑通** | | C 端 | `mini-user` 小程序 + h5-user;v3.4.13 版本门控/门店/物流已上 | -| 近期版本 | v3.4.10 套餐 · v3.4.11 开发计划/企微 · v3.4.12 工单迭代 · v3.4.13 体验优化 **已发生产** | +| 近期版本 | … · v3.4.15 门店列表/套餐多图 · **v3.4.16 登录号/联系电话分离(开发中)** | | 冒烟 | `scripts/smoke-v3.mjs` 窄路径 ≠ 全量 ACC | | REQ 明细 | PRD §4 + `.cursor/skills/dukang-v3/reference-req-index.md` | @@ -55,17 +55,16 @@ | 3.4.11 | [`开发计划`](./杜康好客-开发计划功能开发文档-v3.4.11.md) | ✅ | | 3.4.12 | [`工单迭代`](./杜康好客-v3.4.12-工单迭代开发文档.md) | ✅ | | 3.4.13 | [`体验优化`](./杜康好客-v3.4.13-体验优化开发文档.md) | ✅ 生产 | -| 3.4.14 | [`mini-user 门店体验 + 小程序可配置 + 测试白名单`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) | 🔶 开发中 | -| 3.4.15 | [`mini-user 门店列表 + HQ 照片/套餐多图`](./杜康好客-v3.4.15-mini-user门店列表优化.md) | 🔶 开发中 | +| 3.4.14 | [`mini-user 门店体验 + 小程序可配置 + 测试白名单`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) | ✅ 生产 | +| 3.4.15 | [`mini-user 门店列表 + HQ 照片/套餐多图`](./杜康好客-v3.4.15-mini-user门店列表优化.md) | ✅ 生产 | -## 5. 变更记录 +| 2026-08-08 | v3.4.14 / v3.4.15 已发生产,更新状态 | 日期 | 说明 | |------|------| | 2026-08-07 | v3.4.14 增补统一测试白名单(不计账 / 限测可见 / Mock 旁路) | | 2026-08-07 | v3.4.15 增补:HQ 门店照片替换/删除、套餐多图上限 20 | -| 2026-08-06 | v3.4.15 mini-user 门店列表卡片优化(开发中) | -| 2026-08-06 | v3.4.14 mini-user 门头/套餐详情(开发中) | + | 2026-08-05 | v3.4.13 | | 2026-08-04 | v3.4.11 / v3.4.12 | | 2026-07-11 | 首版对照表 |