diff --git a/apps/admin-web/src/pages/CityPartnersPage.tsx b/apps/admin-web/src/pages/CityPartnersPage.tsx
index 3a8dbbe..bcf7ab1 100644
--- a/apps/admin-web/src/pages/CityPartnersPage.tsx
+++ b/apps/admin-web/src/pages/CityPartnersPage.tsx
@@ -301,7 +301,16 @@ export default function CityPartnersPage() {
ellipsis: true,
render: (v) => v || '—',
},
- { title: '门店', dataIndex: 'storeCount', width: 60 },
+ {
+ title: '门店',
+ dataIndex: 'storeCount',
+ width: 60,
+ render: (n: number, row) => (
+
+ {n ?? 0}
+
+ ),
+ },
{ title: '子账号', dataIndex: 'accountCount', width: 70, render: (n) => Math.max(0, n - 1) },
{ title: '创建', dataIndex: 'createdAt', width: 150, render: fmtTime },
{
@@ -444,7 +453,11 @@ export default function CityPartnersPage() {
<>
{detail.cityName ?? '—'}
- {detail.storeCount ?? 0}
+
+
+ {detail.storeCount ?? 0}
+
+
{detail.managedWarehouseName ?? (
diff --git a/apps/admin-web/src/pages/StoresPage.tsx b/apps/admin-web/src/pages/StoresPage.tsx
index 305da2a..128d7f1 100644
--- a/apps/admin-web/src/pages/StoresPage.tsx
+++ b/apps/admin-web/src/pages/StoresPage.tsx
@@ -324,12 +324,16 @@ export default function StoresPage() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const initialCityId = searchParams.get('cityId') ?? '';
+ const initialPartnerId = searchParams.get('partnerId') ?? '';
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [createForm] = Form.useForm();
- const [filters, setFilters] = useState>(() =>
- initialCityId ? { cityId: initialCityId } : {},
- );
+ const [filters, setFilters] = useState>(() => {
+ const init: Record = {};
+ if (initialCityId) init.cityId = initialCityId;
+ if (initialPartnerId) init.partnerId = initialPartnerId;
+ return init;
+ });
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList(
'/admin/stores',
() => {
@@ -339,11 +343,13 @@ export default function StoresPage() {
if (filters.auditStatus) qs.set('auditStatus', filters.auditStatus);
if (filters.phone) qs.set('phone', filters.phone);
if (filters.cityId) qs.set('cityId', filters.cityId);
+ if (filters.partnerId) qs.set('partnerId', filters.partnerId);
return qs;
},
[filters],
);
const [filterCities, setFilterCities] = useState([]);
+ const [filterPartners, setFilterPartners] = useState([]);
const [detail, setDetail] = useState | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [rejectOpen, setRejectOpen] = useState(false);
@@ -366,19 +372,34 @@ export default function StoresPage() {
void request>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
.then((res) => setFilterCities(res.items))
.catch(() => {});
+ void request>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
+ .then((res) => setFilterPartners(res.items))
+ .catch(() => {});
}, []);
useEffect(() => {
const cityId = searchParams.get('cityId') ?? '';
+ const partnerId = searchParams.get('partnerId') ?? '';
+ let changed = false;
setFilters((prev) => {
- if ((prev.cityId ?? '') === cityId) return prev;
const next = { ...prev };
- if (cityId) next.cityId = cityId;
- else delete next.cityId;
- return next;
+ if ((prev.cityId ?? '') !== cityId) {
+ changed = true;
+ if (cityId) next.cityId = cityId;
+ else delete next.cityId;
+ }
+ if ((prev.partnerId ?? '') !== partnerId) {
+ changed = true;
+ if (partnerId) next.partnerId = partnerId;
+ else delete next.partnerId;
+ }
+ return changed ? next : prev;
});
- form.setFieldsValue({ cityId: cityId || undefined });
- if (cityId) setPage(1);
+ form.setFieldsValue({
+ cityId: cityId || undefined,
+ partnerId: partnerId || undefined,
+ });
+ if (cityId || partnerId) setPage(1);
}, [searchParams, form, setPage]);
const selectedPartnerId = Form.useWatch('partnerAccountId', createForm);
@@ -452,7 +473,12 @@ export default function StoresPage() {
// 以门店手机号为准保存;若与账号登录号不一致,保存时会强制同步到登录账号
phone: storePhone || loginPhone,
intro: d.intro,
- benefitUsageRule: d.benefitUsageRule,
+ benefitUsageRule:
+ d.benefitUsageRule != null &&
+ String(d.benefitUsageRule).trim() &&
+ !/^null$/i.test(String(d.benefitUsageRule).trim())
+ ? String(d.benefitUsageRule)
+ : '',
coverUrl: d.coverUrl,
province: d.province,
city: d.cityName,
@@ -490,7 +516,12 @@ export default function StoresPage() {
phone: v.phone,
coverUrl: v.coverUrl,
intro: v.intro,
- benefitUsageRule: v.benefitUsageRule ?? null,
+ benefitUsageRule:
+ typeof v.benefitUsageRule === 'string' &&
+ v.benefitUsageRule.trim() &&
+ !/^null$/i.test(v.benefitUsageRule.trim())
+ ? v.benefitUsageRule.trim()
+ : null,
categoryId: v.categoryId,
province: v.province,
city: v.city,
@@ -761,6 +792,16 @@ export default function StoresPage() {
options={filterCities.map((c) => ({ value: c.id, label: c.name }))}
/>
+
+
@@ -779,7 +820,9 @@ export default function StoresPage() {
form.resetFields();
setFilters({});
setPage(1);
- if (searchParams.has('cityId')) navigate('/stores', { replace: true });
+ if (searchParams.has('cityId') || searchParams.has('partnerId')) {
+ navigate('/stores', { replace: true });
+ }
}}
>
重置
diff --git a/apps/h5-partner/src/pages/StoreDetailPage.tsx b/apps/h5-partner/src/pages/StoreDetailPage.tsx
index 8e83366..2aa3ace 100644
--- a/apps/h5-partner/src/pages/StoreDetailPage.tsx
+++ b/apps/h5-partner/src/pages/StoreDetailPage.tsx
@@ -67,7 +67,10 @@ export default function StoreDetailPage() {
phone: String(data.phone || ''),
address: String(data.address || ''),
intro: String(data.intro || ''),
- benefitUsageRule: String(data.benefitUsageRule || ''),
+ benefitUsageRule: (() => {
+ const raw = String(data.benefitUsageRule || '').trim();
+ return raw && !/^null$/i.test(raw) ? raw : '';
+ })(),
latitude: data.latitude != null && data.latitude !== '' ? String(data.latitude) : '',
longitude: data.longitude != null && data.longitude !== '' ? String(data.longitude) : '',
});
diff --git a/apps/mini-user/src/pages/store-detail/index.tsx b/apps/mini-user/src/pages/store-detail/index.tsx
index 3c0efec..35ca786 100644
--- a/apps/mini-user/src/pages/store-detail/index.tsx
+++ b/apps/mini-user/src/pages/store-detail/index.tsx
@@ -174,7 +174,9 @@ export default function StoreDetailPage() {
]);
const intro = store.intro?.trim() || '';
- const benefitRule = store.benefitUsageRule?.trim() || '';
+ const benefitRuleRaw = store.benefitUsageRule?.trim() || '';
+ const benefitRule =
+ benefitRuleRaw && !/^null$/i.test(benefitRuleRaw) ? benefitRuleRaw : '';
function previewEnv(index: number) {
if (!envPhotos.length) return;
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 07f0a7c..417fe68 100644
--- a/server/dukang-api/src/modules/ops/admin-stores.service.ts
+++ b/server/dukang-api/src/modules/ops/admin-stores.service.ts
@@ -17,6 +17,14 @@ import type {
UpdateStoreStatusDto,
} from './dto/admin-mutate.dto';
+/** 选填文案:空 / null / "null" 一律存库为 null,避免 String(null)==="null" */
+function normalizeStoreOptionalText(value: unknown): string | null {
+ if (value == null) return null;
+ const s = String(value).trim();
+ if (!s || /^null$/i.test(s) || /^undefined$/i.test(s)) return null;
+ return s;
+}
+
@Injectable()
export class AdminStoresService {
constructor(
@@ -286,7 +294,7 @@ export class AdminStoresService {
...(normalizedPhone !== undefined ? { phone: normalizedPhone } : {}),
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
...(dto.benefitUsageRule !== undefined
- ? { benefitUsageRule: String(dto.benefitUsageRule).trim() || null }
+ ? { benefitUsageRule: normalizeStoreOptionalText(dto.benefitUsageRule) }
: {}),
...(dto.address !== undefined ? { address: dto.address.trim() } : {}),
...(dto.province !== undefined ? { province: dto.province.trim() } : {}),
@@ -417,7 +425,7 @@ export class AdminStoresService {
if (intro && (intro.length < 2 || intro.length > 500)) {
throw new BadRequestException('门店简介须为 2~500 字');
}
- const benefitUsageRule = dto.benefitUsageRule?.trim() || null;
+ const benefitUsageRule = normalizeStoreOptionalText(dto.benefitUsageRule);
if (benefitUsageRule && benefitUsageRule.length > 1000) {
throw new BadRequestException('好客权益券使用规则最多 1000 字');
}
diff --git a/server/dukang-api/src/modules/store/store.service.ts b/server/dukang-api/src/modules/store/store.service.ts
index e38aed8..cb7ae83 100644
--- a/server/dukang-api/src/modules/store/store.service.ts
+++ b/server/dukang-api/src/modules/store/store.service.ts
@@ -28,6 +28,14 @@ function haversineMeters(lat1: number, lng1: number, lat2: number, lng2: number)
return 2 * R * Math.asin(Math.min(1, Math.sqrt(a)));
}
+/** 选填文案:空 / null / "null" 存库为 null,避免 String(null)==="null" */
+function normalizeOptionalTextField(value: unknown): string | null {
+ if (value == null) return null;
+ const s = String(value).trim();
+ if (!s || /^null$/i.test(s) || /^undefined$/i.test(s)) return null;
+ return s;
+}
+
function parseOptionalCoord(value: unknown, kind: 'lat' | 'lng' = 'lng'): number | null {
if (value == null || value === '') return null;
const n = typeof value === 'number' ? value : Number(value);
@@ -301,9 +309,8 @@ export class StoreService {
if (introRaw && (introRaw.length < 2 || introRaw.length > 500)) {
throw new BadRequestException('门店简介须为 2~500 字');
}
- const benefitUsageRuleRaw =
- body.benefitUsageRule != null ? String(body.benefitUsageRule).trim() : '';
- if (benefitUsageRuleRaw.length > 1000) {
+ const benefitUsageRuleRaw = normalizeOptionalTextField(body.benefitUsageRule);
+ if (benefitUsageRuleRaw && benefitUsageRuleRaw.length > 1000) {
throw new BadRequestException('好客权益券使用规则最多 1000 字');
}
@@ -325,7 +332,7 @@ export class StoreService {
district: String(body.district ?? ''),
address: String(body.address),
intro: introRaw || null,
- benefitUsageRule: benefitUsageRuleRaw || null,
+ benefitUsageRule: benefitUsageRuleRaw,
avgPrice: avgPriceRaw,
openTime,
closeTime,
@@ -522,7 +529,9 @@ export class StoreService {
const address = body.address !== undefined ? String(body.address).trim() : undefined;
const introRaw = body.intro !== undefined ? String(body.intro).trim() : undefined;
const benefitUsageRuleRaw =
- body.benefitUsageRule !== undefined ? String(body.benefitUsageRule).trim() : undefined;
+ body.benefitUsageRule !== undefined
+ ? normalizeOptionalTextField(body.benefitUsageRule)
+ : undefined;
const latitude =
body.latitude !== undefined ? parseOptionalCoord(body.latitude, 'lat') : undefined;
const longitude =
@@ -536,7 +545,7 @@ export class StoreService {
if (introRaw && (introRaw.length < 2 || introRaw.length > 500)) {
throw new BadRequestException('门店简介须为 2~500 字');
}
- if (benefitUsageRuleRaw !== undefined && benefitUsageRuleRaw.length > 1000) {
+ if (benefitUsageRuleRaw != null && benefitUsageRuleRaw.length > 1000) {
throw new BadRequestException('好客权益券使用规则最多 1000 字');
}
if (
@@ -561,7 +570,7 @@ export class StoreService {
...(hasCoordsUpdate ? { latitude, longitude } : {}),
...(introRaw !== undefined ? { intro: introRaw || null } : {}),
...(benefitUsageRuleRaw !== undefined
- ? { benefitUsageRule: benefitUsageRuleRaw || null }
+ ? { benefitUsageRule: benefitUsageRuleRaw }
: {}),
...(resubmitAudit
? {