This commit is contained in:
@@ -368,12 +368,18 @@ export default function StoresPage() {
|
|||||||
account?.phone ||
|
account?.phone ||
|
||||||
(typeof d.phone === 'string' ? d.phone : undefined);
|
(typeof d.phone === 'string' ? d.phone : undefined);
|
||||||
const storePhone = 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);
|
const phoneMismatchNow = !!(loginPhone && storePhone && loginPhone !== storePhone);
|
||||||
setPhoneMismatch(phoneMismatchNow ? String(loginPhone) : null);
|
setPhoneMismatch(phoneMismatchNow ? String(loginPhone) : null);
|
||||||
editForm.setFieldsValue({
|
editForm.setFieldsValue({
|
||||||
name: d.name,
|
name: d.name,
|
||||||
// 以门店手机号为准保存;若与账号登录号不一致,保存时会强制同步到登录账号
|
// 登录手机号(老板);与 StoreAccount 同步
|
||||||
phone: storePhone || loginPhone,
|
phone: loginPhone || storePhone,
|
||||||
|
contactPhone,
|
||||||
intro: d.intro,
|
intro: d.intro,
|
||||||
benefitUsageRule:
|
benefitUsageRule:
|
||||||
d.benefitUsageRule != null &&
|
d.benefitUsageRule != null &&
|
||||||
@@ -431,6 +437,7 @@ export default function StoresPage() {
|
|||||||
const payload = {
|
const payload = {
|
||||||
name: v.name,
|
name: v.name,
|
||||||
phone: v.phone,
|
phone: v.phone,
|
||||||
|
contactPhone: String(v.contactPhone || '').trim() || v.phone,
|
||||||
coverUrl: v.coverUrl ?? '',
|
coverUrl: v.coverUrl ?? '',
|
||||||
envPhotoUrls: Array.isArray(v.envPhotoUrls)
|
envPhotoUrls: Array.isArray(v.envPhotoUrls)
|
||||||
? v.envPhotoUrls.map((u: string) => String(u || '').trim()).filter(Boolean)
|
? v.envPhotoUrls.map((u: string) => String(u || '').trim()).filter(Boolean)
|
||||||
@@ -608,6 +615,7 @@ export default function StoresPage() {
|
|||||||
city: values.city,
|
city: values.city,
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
phone: values.phone.trim(),
|
phone: values.phone.trim(),
|
||||||
|
contactPhone: String(values.contactPhone || values.phone || '').trim(),
|
||||||
district: values.district.trim(),
|
district: values.district.trim(),
|
||||||
address: values.address.trim(),
|
address: values.address.trim(),
|
||||||
...(values.latitude != null &&
|
...(values.latitude != null &&
|
||||||
@@ -673,7 +681,13 @@ export default function StoresPage() {
|
|||||||
render: (_, row) => row.category?.name || '—',
|
render: (_, row) => row.category?.name || '—',
|
||||||
},
|
},
|
||||||
{ title: '城市', dataIndex: 'cityName', width: 80 },
|
{ 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,
|
title: '营业状态', dataIndex: 'status', width: 90,
|
||||||
render: (s) => <Tag>{STORE_STATUS_LABELS[s] || s}</Tag>,
|
render: (s) => <Tag>{STORE_STATUS_LABELS[s] || s}</Tag>,
|
||||||
@@ -884,18 +898,26 @@ export default function StoresPage() {
|
|||||||
type="warning"
|
type="warning"
|
||||||
showIcon
|
showIcon
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
message={`登录账号手机号仍为 ${phoneMismatch},与门店手机号不一致。请点击右上角「保存修改」同步,否则门店端无法用新号登录。`}
|
message={`主账号登录号为 ${phoneMismatch},与门店登录字段不一致。保存「登录手机号」将同步到门店端登录账号。`}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Form.Item name="name" label="名称" rules={[{ required: true }]}><Input /></Form.Item>
|
<Form.Item name="name" label="名称" rules={[{ required: true }]}><Input /></Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="phone"
|
name="phone"
|
||||||
label="登录手机号"
|
label="登录手机号(老板)"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
extra="门店端短信登录使用此号码;修改后需用新号重新登录"
|
extra="门店端主账号短信登录;修改后需用新号重新登录"
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="contactPhone"
|
||||||
|
label="联系电话(店长/对外)"
|
||||||
|
rules={[{ required: true, message: '请填写对外联系电话' }]}
|
||||||
|
extra="用户端门店详情展示与拨号使用此号码,可与登录号不同"
|
||||||
|
>
|
||||||
|
<Input placeholder="11位手机号" />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="categoryParentId"
|
name="categoryParentId"
|
||||||
label="门店分类(大类)"
|
label="门店分类(大类)"
|
||||||
@@ -1171,8 +1193,15 @@ export default function StoresPage() {
|
|||||||
<Form.Item name="name" label="门店名称" rules={[{ required: true, message: '请填写门店名称' }]}>
|
<Form.Item name="name" label="门店名称" rules={[{ required: true, message: '请填写门店名称' }]}>
|
||||||
<Input placeholder="请输入门店名称" />
|
<Input placeholder="请输入门店名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="phone" label="门店手机号(登录账号)" rules={[{ required: true, message: '请填写门店手机号' }]}>
|
<Form.Item name="phone" label="登录手机号(老板)" rules={[{ required: true, message: '请填写登录手机号' }]}>
|
||||||
<Input placeholder="11位手机号" />
|
<Input placeholder="门店端主账号登录" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="contactPhone"
|
||||||
|
label="联系电话(店长/对外)"
|
||||||
|
extra="用户端拨号展示;留空则与登录号相同"
|
||||||
|
>
|
||||||
|
<Input placeholder="11位手机号,可与登录号不同" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="sortOrder" label="排序" extra="数值越小越靠前">
|
<Form.Item name="sortOrder" label="排序" extra="数值越小越靠前">
|
||||||
<InputNumber min={0} precision={0} style={{ width: '100%' }} placeholder="0" />
|
<InputNumber min={0} precision={0} style={{ width: '100%' }} placeholder="0" />
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ export type StoreDraftForm = {
|
|||||||
city: string;
|
city: string;
|
||||||
district: string;
|
district: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
/** 门店登录手机号(老板主账号) */
|
||||||
phone: string;
|
phone: string;
|
||||||
|
/** 对外联系电话(店长);可与登录号不同 */
|
||||||
|
contactPhone: string;
|
||||||
address: string;
|
address: string;
|
||||||
/** 门店坐标(定位或地理编码) */
|
/** 门店坐标(定位或地理编码) */
|
||||||
latitude: string;
|
latitude: string;
|
||||||
@@ -51,6 +54,7 @@ export const defaultStoreForm = (): StoreDraftForm => ({
|
|||||||
cityId: '',
|
cityId: '',
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
|
contactPhone: '',
|
||||||
address: '',
|
address: '',
|
||||||
latitude: '',
|
latitude: '',
|
||||||
longitude: '',
|
longitude: '',
|
||||||
@@ -102,6 +106,9 @@ export function normalizeStoreDraftForm(raw: Partial<StoreDraftForm> | null | un
|
|||||||
...raw,
|
...raw,
|
||||||
regionCodes: Array.isArray(raw.regionCodes) ? raw.regionCodes.map(String) : base.regionCodes,
|
regionCodes: Array.isArray(raw.regionCodes) ? raw.regionCodes.map(String) : base.regionCodes,
|
||||||
cityId: String(raw.cityId ?? base.cityId),
|
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,
|
latitude: raw.latitude != null && raw.latitude !== '' ? String(raw.latitude) : base.latitude,
|
||||||
longitude: raw.longitude != null && raw.longitude !== '' ? String(raw.longitude) : base.longitude,
|
longitude: raw.longitude != null && raw.longitude !== '' ? String(raw.longitude) : base.longitude,
|
||||||
openTime: String(raw.openTime ?? base.openTime),
|
openTime: String(raw.openTime ?? base.openTime),
|
||||||
@@ -236,14 +243,16 @@ export function patchEnvPhotoAt(urls: string[], index: number, url: string): str
|
|||||||
export function validateStoreStep3(
|
export function validateStoreStep3(
|
||||||
form: Pick<
|
form: Pick<
|
||||||
StoreDraftForm,
|
StoreDraftForm,
|
||||||
'bankAccountName' | 'bankAccountNo' | 'bankBranch' | 'phone'
|
'bankAccountName' | 'bankAccountNo' | 'bankBranch' | 'phone' | 'contactPhone'
|
||||||
>,
|
>,
|
||||||
): string | null {
|
): string | null {
|
||||||
if (!form.bankAccountName.trim()) return '请填写户主姓名';
|
if (!form.bankAccountName.trim()) return '请填写户主姓名';
|
||||||
if (!form.bankAccountNo.trim()) return '请填写银行卡号';
|
if (!form.bankAccountNo.trim()) return '请填写银行卡号';
|
||||||
if (!BANK_RE.test(form.bankAccountNo.replace(/\s/g, ''))) return '银行卡号须为 16~19 位数字';
|
if (!BANK_RE.test(form.bankAccountNo.replace(/\s/g, ''))) return '银行卡号须为 16~19 位数字';
|
||||||
if (!form.bankBranch.trim()) return '请填写开户支行';
|
if (!form.bankBranch.trim()) return '请填写开户支行';
|
||||||
if (!form.phone.trim()) return '请填写联系电话';
|
if (!form.phone.trim()) return '请填写门店登录手机号';
|
||||||
if (!PHONE_RE.test(form.phone.trim())) return '联系电话须为11位手机号';
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ type StoreCategoryNode = {
|
|||||||
|
|
||||||
type FieldErrors = {
|
type FieldErrors = {
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
contactPhone?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -237,9 +238,17 @@ export default function StoreCreatePage() {
|
|||||||
|
|
||||||
setSubmitError('');
|
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);
|
const msg = validateStoreStep3(form);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
if (isPhoneValidationMessage(msg)) {
|
if (isPhoneValidationMessage(msg)) {
|
||||||
setFieldErrors({ phone: msg });
|
setFieldErrors(
|
||||||
|
msg.includes('联系电话') ? { contactPhone: msg } : { phone: msg },
|
||||||
|
);
|
||||||
reportStepError(msg);
|
reportStepError(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -336,7 +347,9 @@ export default function StoreCreatePage() {
|
|||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
if (isPhoneValidationMessage(msg)) {
|
if (isPhoneValidationMessage(msg)) {
|
||||||
setFieldErrors({ phone: msg });
|
setFieldErrors(
|
||||||
|
msg.includes('联系电话') ? { contactPhone: msg } : { phone: msg },
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reportFormError(msg);
|
reportFormError(msg);
|
||||||
@@ -441,6 +454,8 @@ export default function StoreCreatePage() {
|
|||||||
|
|
||||||
phone: form.phone.trim(),
|
phone: form.phone.trim(),
|
||||||
|
|
||||||
|
contactPhone: form.contactPhone.trim() || form.phone.trim(),
|
||||||
|
|
||||||
district: form.district.trim(),
|
district: form.district.trim(),
|
||||||
|
|
||||||
address: form.address.trim(),
|
address: form.address.trim(),
|
||||||
@@ -1089,7 +1104,43 @@ export default function StoreCreatePage() {
|
|||||||
|
|
||||||
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
该手机号将作为门店端登录账号。
|
老板手机号,作为门店端主账号登录凭证。
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="partner-field">
|
||||||
|
|
||||||
|
<label>联系电话 <span className="text-primary">*</span></label>
|
||||||
|
|
||||||
|
<div className="partner-field-input">
|
||||||
|
|
||||||
|
<span className="material-symbols-outlined">phone_in_talk</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
|
||||||
|
type="tel"
|
||||||
|
|
||||||
|
placeholder="店长或对外可拨打号码"
|
||||||
|
|
||||||
|
value={form.contactPhone}
|
||||||
|
|
||||||
|
onChange={(e) => patchForm({ contactPhone: e.target.value })}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{fieldErrors.contactPhone && (
|
||||||
|
|
||||||
|
<p className="partner-field-error" role="alert">{fieldErrors.contactPhone}</p>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||||
|
|
||||||
|
用户端门店详情展示与拨号使用此号码,可与登录号不同。
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export default function StoreDetailPage() {
|
|||||||
const [loadError, setLoadError] = useState('');
|
const [loadError, setLoadError] = useState('');
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
loginPhone: '',
|
||||||
|
contactPhone: '',
|
||||||
address: '',
|
address: '',
|
||||||
intro: '',
|
intro: '',
|
||||||
benefitUsageRule: '',
|
benefitUsageRule: '',
|
||||||
@@ -64,7 +65,8 @@ export default function StoreDetailPage() {
|
|||||||
setStore(data);
|
setStore(data);
|
||||||
setForm({
|
setForm({
|
||||||
name: String(data.name || ''),
|
name: String(data.name || ''),
|
||||||
phone: String(data.phone || ''),
|
loginPhone: String(data.phone || ''),
|
||||||
|
contactPhone: String(data.contactPhone || data.phone || ''),
|
||||||
address: String(data.address || ''),
|
address: String(data.address || ''),
|
||||||
intro: String(data.intro || ''),
|
intro: String(data.intro || ''),
|
||||||
benefitUsageRule: (() => {
|
benefitUsageRule: (() => {
|
||||||
@@ -164,7 +166,7 @@ export default function StoreDetailPage() {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: form.name.trim(),
|
name: form.name.trim(),
|
||||||
phone: form.phone.trim(),
|
contactPhone: form.contactPhone.trim(),
|
||||||
address: form.address.trim(),
|
address: form.address.trim(),
|
||||||
intro: form.intro.trim(),
|
intro: form.intro.trim(),
|
||||||
benefitUsageRule: form.benefitUsageRule.trim() || null,
|
benefitUsageRule: form.benefitUsageRule.trim() || null,
|
||||||
@@ -346,12 +348,30 @@ export default function StoreDetailPage() {
|
|||||||
<label>门店名称</label>
|
<label>门店名称</label>
|
||||||
<input disabled={readOnly} value={form.name} onChange={(e) => 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 }} />
|
<input disabled={readOnly} value={form.name} onChange={(e) => 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 }} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="partner-field">
|
||||||
|
<label>门店登录手机号</label>
|
||||||
|
<div className="partner-field-input">
|
||||||
|
<span className="material-symbols-outlined">lock</span>
|
||||||
|
<input disabled type="tel" value={form.loginPhone} readOnly />
|
||||||
|
</div>
|
||||||
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||||
|
老板主账号,用于门店端登录;如需变更请联系总部。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="partner-field">
|
<div className="partner-field">
|
||||||
<label>联系电话</label>
|
<label>联系电话</label>
|
||||||
<div className="partner-field-input">
|
<div className="partner-field-input">
|
||||||
<span className="material-symbols-outlined">call</span>
|
<span className="material-symbols-outlined">phone_in_talk</span>
|
||||||
<input disabled={readOnly} type="tel" value={form.phone} onChange={(e) => setForm({ ...form, phone: e.target.value })} />
|
<input
|
||||||
|
disabled={readOnly}
|
||||||
|
type="tel"
|
||||||
|
value={form.contactPhone}
|
||||||
|
onChange={(e) => setForm({ ...form, contactPhone: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||||
|
店长或对外展示号码,用户端拨号使用此号码。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="partner-field">
|
<div className="partner-field">
|
||||||
<label>门店地址</label>
|
<label>门店地址</label>
|
||||||
|
|||||||
@@ -1248,7 +1248,10 @@ model Store {
|
|||||||
partnerAccountId BigInt @map("partner_account_id") @db.UnsignedBigInt
|
partnerAccountId BigInt @map("partner_account_id") @db.UnsignedBigInt
|
||||||
categoryId BigInt? @map("category_id") @db.UnsignedBigInt
|
categoryId BigInt? @map("category_id") @db.UnsignedBigInt
|
||||||
name String @db.VarChar(128)
|
name String @db.VarChar(128)
|
||||||
|
/// 门店主账号登录手机号(老板);与 StoreAccount.phone 对齐
|
||||||
phone String @db.VarChar(20)
|
phone String @db.VarChar(20)
|
||||||
|
/// 对外联系电话(店长等);C 端拨号展示用;空则回退 phone
|
||||||
|
contactPhone String? @map("contact_phone") @db.VarChar(20)
|
||||||
province String @db.VarChar(32)
|
province String @db.VarChar(32)
|
||||||
cityName String @map("city_name") @db.VarChar(32)
|
cityName String @map("city_name") @db.VarChar(32)
|
||||||
district String @db.VarChar(32)
|
district String @db.VarChar(32)
|
||||||
|
|||||||
@@ -44,11 +44,36 @@ export function mapOrderCompat<T extends OrderLike & {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapStoreCompat<T extends { coverResource?: { url?: string } | null }>(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,
|
...store,
|
||||||
coverUrl: store.coverResource?.url ?? null,
|
coverUrl: store.coverResource?.url ?? null,
|
||||||
|
contactPhone,
|
||||||
};
|
};
|
||||||
|
if (opts?.publicDial) {
|
||||||
|
return {
|
||||||
|
...mapped,
|
||||||
|
phone: contactPhone || store.phone || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapStatusLogCompat(events: CommonEvent[]) {
|
export function mapStatusLogCompat(events: CommonEvent[]) {
|
||||||
|
|||||||
@@ -146,8 +146,10 @@ export class AdminStoresService {
|
|||||||
visibilityPhones: visibilityPhones.map((p) => p.phone),
|
visibilityPhones: visibilityPhones.map((p) => p.phone),
|
||||||
partner: store.partnerAccount,
|
partner: store.partnerAccount,
|
||||||
account: store.bindings[0]?.storeAccount ?? null,
|
account: store.bindings[0]?.storeAccount ?? null,
|
||||||
/** 门店端登录手机号(store_account.phone),与 store.phone 应对齐 */
|
/** 门店端登录手机号(store_account.phone);与 store.phone 对齐 */
|
||||||
loginPhone: store.bindings[0]?.storeAccount?.phone ?? store.phone,
|
loginPhone: store.bindings[0]?.storeAccount?.phone ?? store.phone,
|
||||||
|
/** 对外联系电话;空则回退登录号 */
|
||||||
|
contactPhone: store.contactPhone?.trim() || store.phone,
|
||||||
bindings: undefined,
|
bindings: undefined,
|
||||||
media,
|
media,
|
||||||
audits,
|
audits,
|
||||||
@@ -275,11 +277,19 @@ export class AdminStoresService {
|
|||||||
if (dto.phone !== undefined) {
|
if (dto.phone !== undefined) {
|
||||||
const normalizedPhone = dto.phone.trim();
|
const normalizedPhone = dto.phone.trim();
|
||||||
if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
|
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 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) {
|
if (dto.visibilityWhitelistEnabled !== undefined || dto.visibilityPhones !== undefined) {
|
||||||
const nextEnabled =
|
const nextEnabled =
|
||||||
dto.visibilityWhitelistEnabled !== undefined
|
dto.visibilityWhitelistEnabled !== undefined
|
||||||
@@ -296,7 +306,7 @@ export class AdminStoresService {
|
|||||||
const needAccountSync =
|
const needAccountSync =
|
||||||
normalizedPhone !== undefined || dto.name !== undefined || bankTouched;
|
normalizedPhone !== undefined || dto.name !== undefined || bankTouched;
|
||||||
|
|
||||||
// 登录凭证在 store_account.phone;必须与门店展示手机号同步
|
// 登录凭证在 store_account.phone;与 store.phone(老板登录号)同步,不与对外联系电话绑定
|
||||||
let primaryBinding: {
|
let primaryBinding: {
|
||||||
storeAccount: { id: bigint; phone: string; name: string } | null;
|
storeAccount: { id: bigint; phone: string; name: string } | null;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
@@ -346,6 +356,9 @@ export class AdminStoresService {
|
|||||||
data: {
|
data: {
|
||||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
||||||
...(normalizedPhone !== undefined ? { phone: normalizedPhone } : {}),
|
...(normalizedPhone !== undefined ? { phone: normalizedPhone } : {}),
|
||||||
|
...(normalizedContactPhone !== undefined
|
||||||
|
? { contactPhone: normalizedContactPhone }
|
||||||
|
: {}),
|
||||||
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
|
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
|
||||||
...(dto.benefitUsageRule !== undefined
|
...(dto.benefitUsageRule !== undefined
|
||||||
? { benefitUsageRule: normalizeStoreOptionalText(dto.benefitUsageRule) }
|
? { benefitUsageRule: normalizeStoreOptionalText(dto.benefitUsageRule) }
|
||||||
@@ -562,6 +575,11 @@ export class AdminStoresService {
|
|||||||
? !!dto.isTest
|
? !!dto.isTest
|
||||||
: await this.testWhitelist.isPhoneInWhitelist(normalizedPhone);
|
: 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({
|
const store = await this.prisma.store.create({
|
||||||
data: {
|
data: {
|
||||||
cityId: city.id,
|
cityId: city.id,
|
||||||
@@ -570,6 +588,7 @@ export class AdminStoresService {
|
|||||||
categoryId,
|
categoryId,
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
phone: normalizedPhone,
|
phone: normalizedPhone,
|
||||||
|
contactPhone: contactPhoneRaw,
|
||||||
province: dto.province ?? city.province,
|
province: dto.province ?? city.province,
|
||||||
cityName: dto.city ?? city.name,
|
cityName: dto.city ?? city.name,
|
||||||
district: dto.district ?? '',
|
district: dto.district ?? '',
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ export class CreateStoreDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|
||||||
|
/** 对外联系电话(店长);不传则默认与登录手机号相同 */
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
contactPhone?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
@@ -162,6 +167,11 @@ export class UpdateStoreDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
|
||||||
|
/** 对外联系电话(店长) */
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
contactPhone?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
intro?: string;
|
intro?: string;
|
||||||
|
|||||||
@@ -179,11 +179,14 @@ export class StoreService {
|
|||||||
for (const store of visible) {
|
for (const store of visible) {
|
||||||
const coords = await this.ensureStoreCoordinates(store);
|
const coords = await this.ensureStoreCoordinates(store);
|
||||||
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
|
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
|
||||||
const mapped = mapStoreCompat({
|
const mapped = mapStoreCompat(
|
||||||
...rest,
|
{
|
||||||
latitude: coords?.latitude ?? store.latitude,
|
...rest,
|
||||||
longitude: coords?.longitude ?? store.longitude,
|
latitude: coords?.latitude ?? store.latitude,
|
||||||
});
|
longitude: coords?.longitude ?? store.longitude,
|
||||||
|
},
|
||||||
|
{ publicDial: true },
|
||||||
|
);
|
||||||
const distanceMeters =
|
const distanceMeters =
|
||||||
hasUser && coords
|
hasUser && coords
|
||||||
? Math.round(haversineMeters(userLat!, userLng!, coords.latitude, coords.longitude))
|
? Math.round(haversineMeters(userLat!, userLng!, coords.latitude, coords.longitude))
|
||||||
@@ -230,29 +233,32 @@ export class StoreService {
|
|||||||
});
|
});
|
||||||
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
|
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
|
||||||
return serializeBigInt(
|
return serializeBigInt(
|
||||||
mapStoreCompat({
|
mapStoreCompat(
|
||||||
...rest,
|
{
|
||||||
latitude: coords?.latitude ?? store.latitude,
|
...rest,
|
||||||
longitude: coords?.longitude ?? store.longitude,
|
latitude: coords?.latitude ?? store.latitude,
|
||||||
media,
|
longitude: coords?.longitude ?? store.longitude,
|
||||||
packages: packageRows.map((p) => {
|
media,
|
||||||
const imageUrls = normalizeStorePackageImageUrls({
|
packages: packageRows.map((p) => {
|
||||||
imageUrl: p.imageUrl,
|
const imageUrls = normalizeStorePackageImageUrls({
|
||||||
imageUrls: p.imageUrls,
|
imageUrl: p.imageUrl,
|
||||||
});
|
imageUrls: p.imageUrls,
|
||||||
return {
|
});
|
||||||
name: p.name,
|
return {
|
||||||
price: p.price.toFixed(2),
|
name: p.name,
|
||||||
dishes: p.dishes,
|
price: p.price.toFixed(2),
|
||||||
usableTime: p.usableTime,
|
dishes: p.dishes,
|
||||||
otherNotes: p.otherNotes,
|
usableTime: p.usableTime,
|
||||||
imageUrl: imageUrls[0] ?? null,
|
otherNotes: p.otherNotes,
|
||||||
/** C 端始终返回数组,便于多图轮播 */
|
imageUrl: imageUrls[0] ?? null,
|
||||||
imageUrls,
|
/** C 端始终返回数组,便于多图轮播 */
|
||||||
sortOrder: p.sortOrder,
|
imageUrls,
|
||||||
};
|
sortOrder: p.sortOrder,
|
||||||
}),
|
};
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
|
{ publicDial: true },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +281,7 @@ export class StoreService {
|
|||||||
include: { category: true, coverResource: true },
|
include: { category: true, coverResource: true },
|
||||||
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
|
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
|
||||||
});
|
});
|
||||||
return serializeBigInt(stores.map(mapStoreCompat));
|
return serializeBigInt(stores.map((s) => mapStoreCompat(s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async partnerGetStore(partnerAccountId: bigint, storeId: bigint) {
|
async partnerGetStore(partnerAccountId: bigint, storeId: bigint) {
|
||||||
@@ -307,10 +313,10 @@ export class StoreService {
|
|||||||
async partnerCheckStorePhone(phone: string) {
|
async partnerCheckStorePhone(phone: string) {
|
||||||
const normalizedPhone = phone.trim();
|
const normalizedPhone = phone.trim();
|
||||||
if (!normalizedPhone) {
|
if (!normalizedPhone) {
|
||||||
return { available: false, message: '请填写联系电话' };
|
return { available: false, message: '请填写门店登录手机号' };
|
||||||
}
|
}
|
||||||
if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
|
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({
|
const existingAccount = await this.prisma.storeAccount.findUnique({
|
||||||
where: { phone: normalizedPhone },
|
where: { phone: normalizedPhone },
|
||||||
@@ -360,6 +366,14 @@ export class StoreService {
|
|||||||
const confirmBindExisting = body.confirmBindExisting === true || body.confirmBindExisting === 'true';
|
const confirmBindExisting = body.confirmBindExisting === true || body.confirmBindExisting === 'true';
|
||||||
await this.assertStorePhoneAvailable(normalizedPhone, confirmBindExisting);
|
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 city = await this.resolvePartnerCity(partnerAccountId, body.cityId);
|
||||||
const coverUrl = body.coverUrl ? String(body.coverUrl).trim() : '';
|
const coverUrl = body.coverUrl ? String(body.coverUrl).trim() : '';
|
||||||
const envPhotoUrls = this.normalizeEnvPhotoUrls(body.envPhotoUrls);
|
const envPhotoUrls = this.normalizeEnvPhotoUrls(body.envPhotoUrls);
|
||||||
@@ -416,6 +430,7 @@ export class StoreService {
|
|||||||
categoryId,
|
categoryId,
|
||||||
name: String(body.name),
|
name: String(body.name),
|
||||||
phone: normalizedPhone,
|
phone: normalizedPhone,
|
||||||
|
contactPhone: contactPhoneRaw,
|
||||||
province: String(body.province ?? city.province ?? '河南省'),
|
province: String(body.province ?? city.province ?? '河南省'),
|
||||||
cityName: String(body.city ?? city.name ?? '郑州市'),
|
cityName: String(body.city ?? city.name ?? '郑州市'),
|
||||||
district: String(body.district ?? ''),
|
district: String(body.district ?? ''),
|
||||||
@@ -614,7 +629,13 @@ export class StoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = body.name !== undefined ? String(body.name).trim() : undefined;
|
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 address = body.address !== undefined ? String(body.address).trim() : undefined;
|
||||||
const introRaw = body.intro !== undefined ? String(body.intro).trim() : undefined;
|
const introRaw = body.intro !== undefined ? String(body.intro).trim() : undefined;
|
||||||
const benefitUsageRuleRaw =
|
const benefitUsageRuleRaw =
|
||||||
@@ -627,7 +648,7 @@ export class StoreService {
|
|||||||
body.longitude !== undefined ? parseOptionalCoord(body.longitude, 'lng') : undefined;
|
body.longitude !== undefined ? parseOptionalCoord(body.longitude, 'lng') : undefined;
|
||||||
|
|
||||||
if (name !== undefined && !name) throw new BadRequestException('请填写门店名称');
|
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位手机号');
|
throw new BadRequestException('联系电话须为11位手机号');
|
||||||
}
|
}
|
||||||
if (address !== undefined && !address) throw new BadRequestException('请填写详细地址');
|
if (address !== undefined && !address) throw new BadRequestException('请填写详细地址');
|
||||||
@@ -650,7 +671,7 @@ export class StoreService {
|
|||||||
where: { id: storeId },
|
where: { id: storeId },
|
||||||
data: {
|
data: {
|
||||||
...(name !== undefined ? { name } : {}),
|
...(name !== undefined ? { name } : {}),
|
||||||
...(phone !== undefined ? { phone } : {}),
|
...(contactPhone !== undefined ? { contactPhone } : {}),
|
||||||
...(address !== undefined
|
...(address !== undefined
|
||||||
? hasCoordsUpdate
|
? hasCoordsUpdate
|
||||||
? { address }
|
? { address }
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
| 3.4.13 | 08-05 | metrics/核销详情/版本联动 PUBLISHED/mini 体验/H5 OAuth | [`v3.4.13`](./杜康好客-v3.4.13-体验优化开发文档.md) |
|
| 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.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.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.9 | 门店套餐 ≤10 条、独立审核 | v3.4.10 |
|
||||||
| 3.10 | 开发计划/任务/版本/技术支持联动 | v3.4.11 |
|
| 3.10 | 开发计划/任务/版本/技术支持联动 | v3.4.11 |
|
||||||
| 3.11 | 企微智能机器人 + 消息推送 Webhook | v3.4.11 |
|
| 3.11 | 企微智能机器人 + 消息推送 Webhook | v3.4.11 |
|
||||||
|
| 3.12 | **登录手机号 ≠ 对外联系电话** | 见下 |
|
||||||
|
|
||||||
|
### 3.12 门店登录号与对外联系电话
|
||||||
|
|
||||||
|
| 字段 | 含义 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `phone` / 门店登录手机号 | 老板主账号 | 门店端短信登录;`StoreAccount.phone` |
|
||||||
|
| `contactPhone` / 联系电话 | 店长等对外号码 | C 端门店详情拨号与展示 |
|
||||||
|
|
||||||
|
- 合伙人拓店入驻、门店资料修改均须采集 **联系电话**(可与登录号不同)。
|
||||||
|
- 未填联系电话时,对外展示回退登录手机号(兼容旧数据)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -1,4 +1,4 @@
|
|||||||
# 杜康好客 · V3.0 现状对照
|
# 杜康好客 · V3.0 现状对照
|
||||||
|
|
||||||
> 基准:仅 [`杜康好客-v3-PRD.md`](./杜康好客-v3-PRD.md) · 总部 = **`admin-web`**(非 H5)
|
> 基准:仅 [`杜康好客-v3-PRD.md`](./杜康好客-v3-PRD.md) · 总部 = **`admin-web`**(非 H5)
|
||||||
> 图例:✅ 完成 · 🔶 部分 · ❌ 未做 · ⚠️ 曾冲突已修
|
> 图例:✅ 完成 · 🔶 部分 · ❌ 未做 · ⚠️ 曾冲突已修
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 主链路 | 登录→下单支付→权益→扫码核销→payout→HQ 打款 **可跑通** |
|
| 主链路 | 登录→下单支付→权益→扫码核销→payout→HQ 打款 **可跑通** |
|
||||||
| C 端 | `mini-user` 小程序 + h5-user;v3.4.13 版本门控/门店/物流已上 |
|
| 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 |
|
| 冒烟 | `scripts/smoke-v3.mjs` 窄路径 ≠ 全量 ACC |
|
||||||
| REQ 明细 | PRD §4 + `.cursor/skills/dukang-v3/reference-req-index.md` |
|
| REQ 明细 | PRD §4 + `.cursor/skills/dukang-v3/reference-req-index.md` |
|
||||||
|
|
||||||
@@ -55,17 +55,16 @@
|
|||||||
| 3.4.11 | [`开发计划`](./杜康好客-开发计划功能开发文档-v3.4.11.md) | ✅ |
|
| 3.4.11 | [`开发计划`](./杜康好客-开发计划功能开发文档-v3.4.11.md) | ✅ |
|
||||||
| 3.4.12 | [`工单迭代`](./杜康好客-v3.4.12-工单迭代开发文档.md) | ✅ |
|
| 3.4.12 | [`工单迭代`](./杜康好客-v3.4.12-工单迭代开发文档.md) | ✅ |
|
||||||
| 3.4.13 | [`体验优化`](./杜康好客-v3.4.13-体验优化开发文档.md) | ✅ 生产 |
|
| 3.4.13 | [`体验优化`](./杜康好客-v3.4.13-体验优化开发文档.md) | ✅ 生产 |
|
||||||
| 3.4.14 | [`mini-user 门店体验 + 小程序可配置 + 测试白名单`](./杜康好客-v3.4.14-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) | 🔶 开发中 |
|
| 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.14 增补统一测试白名单(不计账 / 限测可见 / Mock 旁路) |
|
||||||
| 2026-08-07 | v3.4.15 增补:HQ 门店照片替换/删除、套餐多图上限 20 |
|
| 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-05 | v3.4.13 |
|
||||||
| 2026-08-04 | v3.4.11 / v3.4.12 |
|
| 2026-08-04 | v3.4.11 / v3.4.12 |
|
||||||
| 2026-07-11 | 首版对照表 |
|
| 2026-07-11 | 首版对照表 |
|
||||||
|
|||||||
Reference in New Issue
Block a user