feat(v3.4.16): partner UX, package pricing, CS gate, HQ list polish

Stop auto ST from validation_error; show package prices with right-aligned layout; partner store list status/filter and onboard CS QR gate; HQ store table truncation/fixed actions; expose CS config in wechat_mini settings; bind local servers for LAN.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-09 16:00:45 +08:00
parent 07630c9046
commit 46361ec713
21 changed files with 637 additions and 221 deletions
+84 -4
View File
@@ -8,6 +8,7 @@ import OssUploadField from '../components/OssUploadField';
import MultiOssUploadField from '../components/MultiOssUploadField';
import { request } from '../lib/api';
import { fetchClientConfig } from '../lib/wechat-auth';
import { toastError, toastSuccess } from '../lib/toast';
import { resolveRegionBinding } from '../lib/china-region';
@@ -48,6 +49,9 @@ import { normalizePackageFormItems, validatePackageFormItems } from '../lib/stor
const STEPS = ['基本信息', '照片上传', '结算资质', '门店套餐'] as const;
const DEFAULT_PARTNER_ONBOARD_CS_HINT =
'使用问题、提现问题等随时可联系【杜康好客】客服';
type StoreCategoryNode = {
id: string;
name: string;
@@ -98,6 +102,10 @@ export default function StoreCreatePage() {
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
const [submitting, setSubmitting] = useState(false);
const [csAdded, setCsAdded] = useState(false);
const [csQrUrl, setCsQrUrl] = useState<string | null>(null);
const [csHint, setCsHint] = useState(DEFAULT_PARTNER_ONBOARD_CS_HINT);
const [csConfigLoading, setCsConfigLoading] = useState(false);
const [cities, setCities] = useState<OpenCityOption[]>([]);
@@ -149,6 +157,29 @@ export default function StoreCreatePage() {
});
}, [step]);
useEffect(() => {
if (step !== 4) return;
let cancelled = false;
setCsConfigLoading(true);
void fetchClientConfig()
.then((cfg) => {
if (cancelled) return;
setCsQrUrl((cfg.partnerOnboardCsQrUrl ?? '').trim() || null);
setCsHint((cfg.partnerOnboardCsHint ?? '').trim() || DEFAULT_PARTNER_ONBOARD_CS_HINT);
})
.catch(() => {
if (cancelled) return;
setCsQrUrl(null);
setCsHint(DEFAULT_PARTNER_ONBOARD_CS_HINT);
})
.finally(() => {
if (!cancelled) setCsConfigLoading(false);
});
return () => {
cancelled = true;
};
}, [step]);
useEffect(() => {
void fetchPartnerCities()
@@ -342,6 +373,15 @@ export default function StoreCreatePage() {
async function submit(skipPackages = false) {
const qr = (csQrUrl ?? '').trim();
if (!qr) {
reportFormError('客服二维码暂未配置,请联系总部');
return;
}
if (!csAdded) {
reportFormError('请先勾选「我已添加【杜康好客】客服」');
return;
}
const msg = validateStoreStep3(form);
@@ -537,6 +577,7 @@ export default function StoreCreatePage() {
const progress = step === 1 ? 0 : step === 2 ? 33 : step === 3 ? 66 : 100;
const canSubmitOnboard = !!csQrUrl?.trim() && csAdded && !submitting && !csConfigLoading;
const nextDisabled = submitting;
@@ -1153,6 +1194,37 @@ export default function StoreCreatePage() {
{step === 4 && (
<>
<section className="partner-form-card partner-onboard-cs-card">
<div className="partner-section-title">
<div className="partner-section-bar" />
<h2 className="headline-md"></h2>
</div>
<p className="label-md text-muted" style={{ marginBottom: 16, lineHeight: 1.5 }}>
{csHint}
</p>
{csConfigLoading ? (
<p className="body-md text-muted"></p>
) : csQrUrl ? (
<img
className="partner-onboard-cs-qr"
src={csQrUrl}
alt="杜康好客企微客服二维码"
/>
) : (
<p className="partner-form-error" role="alert">
</p>
)}
<label className="partner-onboard-cs-check">
<input
type="checkbox"
checked={csAdded}
disabled={!csQrUrl || submitting}
onChange={(e) => setCsAdded(e.target.checked)}
/>
<span></span>
</label>
</section>
<section className="partner-form-card">
<div className="partner-section-title">
<div className="partner-section-bar" />
@@ -1198,13 +1270,21 @@ export default function StoreCreatePage() {
) : (
<>
<button type="button" className="partner-btn-outline" onClick={() => void submit(true)} disabled={submitting}>
<button
type="button"
className="partner-btn-outline"
onClick={() => void submit(true)}
disabled={!canSubmitOnboard}
>
</button>
<button type="button" className="partner-btn-primary" onClick={() => void submit(false)} disabled={submitting}>
<button
type="button"
className="partner-btn-primary"
onClick={() => void submit(false)}
disabled={!canSubmitOnboard}
>
{submitting ? '提交中…' : '提交'}
</button>
</>