= {
+ PENDING: '待审核',
+ APPROVED: '已通过',
+ REJECTED: '已驳回',
+};
+
function packageKey(pkg: StorePackageItemDto | StorePackageViewDto, index: number) {
const name = String(pkg.name ?? '').trim();
return name ? `name:${name}` : `idx:${index}`;
@@ -75,11 +82,14 @@ const CHANGE_LABELS = {
function PackageDetailCard({
title,
pkg,
+ change,
}: {
title?: string;
pkg: StorePackageItemDto | StorePackageViewDto;
+ change?: keyof typeof CHANGE_LABELS;
}) {
const images = normalizeStorePackageImageUrls(pkg);
+ const meta = change ? CHANGE_LABELS[change] : null;
return (
- {title ? (
-
- {title}
-
- ) : null}
+
+ {title ? (
+ {title}
+ ) : null}
+ {meta ? {meta.text} : null}
+
{pkg.name}
¥{pkg.price}
-
+
{pkg.dishes || '—'}
{pkg.usableTime ? (
-
- 可用时间:{pkg.usableTime}
-
+
+ 可用时间:{pkg.usableTime}
+
) : null}
{pkg.otherNotes ? (
-
- 其他说明:{pkg.otherNotes}
-
+
+ 其他说明:{pkg.otherNotes}
+
) : null}
{images.length ? (
@@ -133,64 +152,19 @@ function PackageDetailCard({
);
}
-function PackageSummaryCell({ pkg }: { pkg?: StorePackageItemDto | StorePackageViewDto }) {
- if (!pkg) return <>—>;
- const images = normalizeStorePackageImageUrls(pkg);
- return (
-
-
- {pkg.name} · ¥{pkg.price}
-
-
{pkg.dishes}
- {pkg.usableTime ? (
-
- 可用:{pkg.usableTime}
-
- ) : null}
- {pkg.otherNotes ? (
-
- 备注:{pkg.otherNotes}
-
- ) : null}
- {images.length ? (
-
-
- {images.slice(0, 4).map((url) => (
-
- ))}
- {images.length > 4 ? (
- +{images.length - 4}
- ) : null}
-
-
- ) : null}
-
- );
-}
-
export default function StorePackageAuditsPage() {
const [loading, setLoading] = useState(false);
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [status, setStatus] = useState('PENDING');
+ const [pendingCount, setPendingCount] = useState(0);
const [rejectOpen, setRejectOpen] = useState(false);
const [rejectReason, setRejectReason] = useState('');
const [activeId, setActiveId] = useState(null);
const [detailOpen, setDetailOpen] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [detail, setDetail] = useState(null);
- const [pkgDetailOpen, setPkgDetailOpen] = useState(false);
- const [pkgDetailTitle, setPkgDetailTitle] = useState('');
- const [pkgDetailList, setPkgDetailList] = useState>(
- [],
- );
async function reload(nextPage = page, nextStatus = status) {
setLoading(true);
@@ -200,12 +174,14 @@ export default function StorePackageAuditsPage() {
pageSize: '20',
});
if (nextStatus) qs.set('status', nextStatus);
- const data = await request>(
- `/admin/store-package-audits?${qs}`,
- );
+ const [data, summary] = await Promise.all([
+ request>(`/admin/store-package-audits?${qs}`),
+ request('/admin/store-package-audits/summary'),
+ ]);
setItems(data.items);
setTotal(data.total);
setPage(data.page);
+ setPendingCount(summary.pendingCount ?? 0);
} catch (e) {
message.error(e instanceof Error ? e.message : '加载失败');
} finally {
@@ -232,15 +208,6 @@ export default function StorePackageAuditsPage() {
}
}
- function openPackageDetails(
- title: string,
- list: Array,
- ) {
- setPkgDetailTitle(title);
- setPkgDetailList(list);
- setPkgDetailOpen(true);
- }
-
async function audit(id: string, action: 'APPROVE' | 'REJECT', reason?: string) {
try {
await request(`/admin/store-package-audits/${id}/audit`, {
@@ -251,6 +218,7 @@ export default function StorePackageAuditsPage() {
});
message.success(action === 'APPROVE' ? '已通过' : '已驳回');
setDetailOpen(false);
+ notifyPackageAuditChanged();
void reload(page, status);
} catch (e) {
message.error(e instanceof Error ? e.message : '操作失败');
@@ -258,26 +226,7 @@ export default function StorePackageAuditsPage() {
}
const diffRows = detail ? diffPackages(detail.livePackages ?? [], detail.packages ?? []) : [];
-
- const diffColumns: ColumnsType<(typeof diffRows)[number]> = [
- {
- title: '变更',
- dataIndex: 'change',
- width: 72,
- render: (v: keyof typeof CHANGE_LABELS) => {
- const meta = CHANGE_LABELS[v];
- return {meta.text};
- },
- },
- {
- title: '当前线上',
- render: (_, row) => ,
- },
- {
- title: '申请变更',
- render: (_, row) => ,
- },
- ];
+ const changeByKey = new Map(diffRows.map((row) => [row.key, row.change]));
const columns: ColumnsType = [
{ title: '门店', dataIndex: 'storeName', render: (_, row) => row.storeName || row.storeId },
@@ -285,7 +234,7 @@ export default function StorePackageAuditsPage() {
title: '状态',
dataIndex: 'status',
render: (v: StorePackageChangeRequestDto['status']) => (
- {STORE_PACKAGE_CHANGE_STATUS_LABELS[v] ?? v}
+ {HQ_PACKAGE_STATUS_LABELS[v] ?? v}
),
},
{
@@ -335,7 +284,15 @@ export default function StorePackageAuditsPage() {
{(['PENDING', 'APPROVED', 'REJECTED', ''] as const).map((s) => (
))}
@@ -380,7 +337,7 @@ export default function StorePackageAuditsPage() {
) : detail ? (
<>
- {STORE_PACKAGE_CHANGE_STATUS_LABELS[detail.status]}
+ {HQ_PACKAGE_STATUS_LABELS[detail.status]}
提交方:{detail.submitterType === 'PARTNER' ? '合伙人' : '门店'} · {fmtTime(detail.createdAt)}
@@ -388,55 +345,49 @@ export default function StorePackageAuditsPage() {
{detail.rejectReason ? (
驳回原因:{detail.rejectReason}
) : null}
-
-
- 线上 {detail.livePackages?.length ?? 0} 条 → 申请 {detail.packages?.length ?? 0} 条
-
-
-
-
-
+
+ 线上已审核 {detail.livePackages?.length ?? 0} 条 · 待审核 {detail.packages?.length ?? 0} 条
+
+
+
+
+ 线上已审核套餐
+
+ {(detail.livePackages ?? []).length ? (
+ (detail.livePackages ?? []).map((pkg, index) => (
+
+ ))
+ ) : (
+
暂无线上套餐
+ )}
+
+
+
+ 待审核套餐
+
+ {(detail.packages ?? []).length ? (
+ (detail.packages ?? []).map((pkg, index) => (
+
+ ))
+ ) : (
+
暂无待审核套餐
+ )}
+
+
>
) : null}
- setPkgDetailOpen(false)}
- footer={null}
- width={720}
- destroyOnClose
- >
- {pkgDetailList.length ? (
- pkgDetailList.map((pkg, index) => (
-
- ))
- ) : (
- 暂无套餐
- )}
-
-
+ isStoreContactPhone(String(value || ''))
+ ? Promise.resolve()
+ : Promise.reject(new Error(STORE_CONTACT_PHONE_HINT)),
+ },
+ ]}
+ extra="用户端门店详情展示与拨号使用此号码,可与登录号不同,支持座机"
>
-
+
{
+ const raw = String(value || '').trim();
+ if (!raw || isStoreContactPhone(raw)) return Promise.resolve();
+ return Promise.reject(new Error(STORE_CONTACT_PHONE_HINT));
+ },
+ },
+ ]}
>
-
+
diff --git a/apps/admin-web/tsconfig.json b/apps/admin-web/tsconfig.json
index 42e0521..308420e 100644
--- a/apps/admin-web/tsconfig.json
+++ b/apps/admin-web/tsconfig.json
@@ -11,7 +11,10 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
- "strict": true
+ "strict": true,
+ "paths": {
+ "@dukang/domain": ["../../packages/domain/src/index.ts"]
+ }
},
"include": ["src"]
}
diff --git a/apps/admin-web/vite.config.ts b/apps/admin-web/vite.config.ts
index 23e81ab..e809b7b 100644
--- a/apps/admin-web/vite.config.ts
+++ b/apps/admin-web/vite.config.ts
@@ -1,10 +1,16 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
+import path from 'path';
const apiTarget = process.env.VITE_API_TARGET ?? 'http://localhost:3010';
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ '@dukang/domain': path.resolve(__dirname, '../../packages/domain/src/index.ts'),
+ },
+ },
server: {
host: true,
port: 5175,
diff --git a/apps/h5-partner/package.json b/apps/h5-partner/package.json
index f188aea..af1565f 100644
--- a/apps/h5-partner/package.json
+++ b/apps/h5-partner/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"@dukang/client-logging": "workspace:*",
+ "@dukang/domain": "workspace:*",
"@dukang/shared-types": "workspace:*",
"@dukang/shared-ui": "workspace:*",
"@dukang/weixin-sdk": "workspace:*",
diff --git a/apps/h5-partner/src/lib/storeDraft.ts b/apps/h5-partner/src/lib/storeDraft.ts
index d52f08d..c0290f6 100644
--- a/apps/h5-partner/src/lib/storeDraft.ts
+++ b/apps/h5-partner/src/lib/storeDraft.ts
@@ -1,3 +1,6 @@
+import { getDefaultPartnerRegionForm } from './china-region';
+import { isStoreContactPhone, STORE_CONTACT_PHONE_HINT } from '@dukang/domain';
+
export type StoreDraftForm = {
regionCodes: string[];
cityId: string;
@@ -48,8 +51,6 @@ export function storeDraftKey(accountId?: string): string {
return accountId ? `${STORE_DRAFT_KEY}_${accountId}` : STORE_DRAFT_KEY;
}
-import { getDefaultPartnerRegionForm } from './china-region';
-
export const defaultStoreForm = (): StoreDraftForm => ({
...getDefaultPartnerRegionForm(),
cityId: '',
@@ -274,6 +275,6 @@ export function validateStoreStep3(
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位手机号';
+ if (!isStoreContactPhone(form.contactPhone.trim())) return STORE_CONTACT_PHONE_HINT;
return null;
}
diff --git a/apps/h5-partner/src/pages/StoreCreatePage.tsx b/apps/h5-partner/src/pages/StoreCreatePage.tsx
index 1b00ab0..cb4f387 100644
--- a/apps/h5-partner/src/pages/StoreCreatePage.tsx
+++ b/apps/h5-partner/src/pages/StoreCreatePage.tsx
@@ -1175,7 +1175,7 @@ export default function StoreCreatePage() {
type="tel"
- placeholder="店长或对外可拨打号码"
+ placeholder="手机号或座机,如 0379-8888888"
value={form.contactPhone}
@@ -1193,7 +1193,7 @@ export default function StoreCreatePage() {
- 用户端门店详情展示与拨号使用此号码,可与登录号不同。
+ 用户端门店详情展示与拨号使用此号码,可与登录号不同,支持座机(如 0379-8888888)。
diff --git a/apps/h5-partner/src/pages/StoreDetailPage.tsx b/apps/h5-partner/src/pages/StoreDetailPage.tsx
index a7b7059..3d4f2c9 100644
--- a/apps/h5-partner/src/pages/StoreDetailPage.tsx
+++ b/apps/h5-partner/src/pages/StoreDetailPage.tsx
@@ -365,12 +365,13 @@ export default function StoreDetailPage() {
setForm({ ...form, contactPhone: e.target.value })}
/>
- 店长或对外展示号码,用户端拨号使用此号码。
+ 店长或对外展示号码,用户端拨号使用此号码,支持座机。
diff --git a/apps/h5-partner/tsconfig.json b/apps/h5-partner/tsconfig.json
index 11f3df4..3acf069 100644
--- a/apps/h5-partner/tsconfig.json
+++ b/apps/h5-partner/tsconfig.json
@@ -13,7 +13,8 @@
"jsx": "react-jsx",
"strict": true,
"paths": {
- "@dukang/shared-ui/*": ["../../packages/shared-ui/src/*"]
+ "@dukang/shared-ui/*": ["../../packages/shared-ui/src/*"],
+ "@dukang/domain": ["../../packages/domain/src/index.ts"]
}
},
"include": ["src"]
diff --git a/apps/h5-partner/vite.config.ts b/apps/h5-partner/vite.config.ts
index 2cf42af..783cc64 100644
--- a/apps/h5-partner/vite.config.ts
+++ b/apps/h5-partner/vite.config.ts
@@ -9,6 +9,7 @@ export default defineConfig({
resolve: {
alias: {
'@dukang/shared-ui': path.resolve(__dirname, '../../packages/shared-ui/src'),
+ '@dukang/domain': path.resolve(__dirname, '../../packages/domain/src/index.ts'),
},
},
server: {
diff --git a/apps/mini-user/src/lib/phone.ts b/apps/mini-user/src/lib/phone.ts
index 2f5bc30..94f0253 100644
--- a/apps/mini-user/src/lib/phone.ts
+++ b/apps/mini-user/src/lib/phone.ts
@@ -21,3 +21,7 @@ export function validateMobilePhone(phone: string): { ok: boolean; message?: str
export function maskPhone(phone: string) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
+
+export function toDialablePhone(raw: string): string {
+ return String(raw ?? '').replace(/[\s-]/g, '');
+}
diff --git a/apps/mini-user/src/pages/store-detail/index.tsx b/apps/mini-user/src/pages/store-detail/index.tsx
index f326b2f..fc50b28 100644
--- a/apps/mini-user/src/pages/store-detail/index.tsx
+++ b/apps/mini-user/src/pages/store-detail/index.tsx
@@ -15,7 +15,7 @@ import ShareNavButton from '../../components/ShareNavButton';
import StoreRedeemMarquee from '../../components/StoreRedeemMarquee';
import WechatShareReady from '../../components/WechatShareReady';
import { request, toast } from '../../lib/api';
-import { maskPhone } from '../../lib/phone';
+import { maskPhone, toDialablePhone } from '../../lib/phone';
import { track } from '../../lib/analytics';
import { formatShanghaiDateTime } from '../../lib/datetime';
import {
@@ -276,7 +276,7 @@ export default function StoreDetailPage() {
return;
}
track('store_phone_call', { storeId: store.id });
- Taro.makePhoneCall({ phoneNumber: store.phone }).catch(() => toast('无法拨打电话'));
+ Taro.makePhoneCall({ phoneNumber: toDialablePhone(store.phone) }).catch(() => toast('无法拨打电话'));
}
function openMap() {
diff --git a/packages/domain/package.json b/packages/domain/package.json
index 864fede..9b45fe8 100644
--- a/packages/domain/package.json
+++ b/packages/domain/package.json
@@ -4,6 +4,14 @@
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./src/index.ts",
+ "require": "./dist/index.js",
+ "default": "./dist/index.js"
+ }
+ },
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts
index 7422ac3..7190649 100644
--- a/packages/domain/src/index.ts
+++ b/packages/domain/src/index.ts
@@ -351,3 +351,4 @@ export function orderTabToStatuses(tab: string): string[] | undefined {
export * from './city-partner';
export * from './dev-plan';
export * from './support-ticket';
+export * from './phone';
diff --git a/packages/domain/src/phone.test.ts b/packages/domain/src/phone.test.ts
new file mode 100644
index 0000000..eeaeee3
--- /dev/null
+++ b/packages/domain/src/phone.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, it } from 'vitest';
+import {
+ isLandlinePhone,
+ isMobilePhone,
+ isStoreContactPhone,
+ normalizeContactPhone,
+ toDialablePhone,
+} from './phone';
+
+describe('store contact phone', () => {
+ it('accepts mainland mobile numbers', () => {
+ expect(isMobilePhone('13800138000')).toBe(true);
+ expect(isStoreContactPhone('13800138000')).toBe(true);
+ expect(isStoreContactPhone(' 13800138000 ')).toBe(true);
+ });
+
+ it('accepts landlines with or without hyphens and optional extension', () => {
+ expect(isLandlinePhone('0379-8888888')).toBe(true);
+ expect(isLandlinePhone('010-12345678')).toBe(true);
+ expect(isLandlinePhone('03798888888')).toBe(true);
+ expect(isLandlinePhone('01012345678')).toBe(true);
+ expect(isLandlinePhone('0379-8888888-12')).toBe(true);
+ expect(isStoreContactPhone('0379-8888 888')).toBe(true);
+ });
+
+ it('rejects login-style invalid and incomplete numbers', () => {
+ expect(isStoreContactPhone('')).toBe(false);
+ expect(isStoreContactPhone('12345678')).toBe(false);
+ expect(isStoreContactPhone('12345678901')).toBe(false);
+ expect(isStoreContactPhone('400-123-4567')).toBe(false);
+ expect(isMobilePhone('0379-8888888')).toBe(false);
+ });
+
+ it('normalizes spaces and strips dial punctuation', () => {
+ expect(normalizeContactPhone(' 0379-8888 888 ')).toBe('0379-8888888');
+ expect(toDialablePhone('0379-8888888')).toBe('03798888888');
+ expect(toDialablePhone('010 1234 5678')).toBe('01012345678');
+ });
+});
diff --git a/packages/domain/src/phone.ts b/packages/domain/src/phone.ts
new file mode 100644
index 0000000..d3a0a72
--- /dev/null
+++ b/packages/domain/src/phone.ts
@@ -0,0 +1,35 @@
+/** 11 位大陆手机号(登录凭证) */
+export const MOBILE_PHONE_RE = /^1[3-9]\d{9}$/;
+
+/**
+ * 国内座机:区号 0 开头(3~4 位),本地号 7~8 位,允许 `-`,可选分机。
+ * 例:0379-8888888、010-12345678、03798888888
+ */
+export const LANDLINE_PHONE_RE = /^0\d{2,3}-?\d{7,8}(-\d{1,6})?$/;
+
+export const STORE_CONTACT_PHONE_HINT = '请输入正确的联系电话(手机号或座机,如 0379-8888888)';
+
+export function normalizeContactPhone(raw: string): string {
+ return String(raw ?? '')
+ .trim()
+ .replace(/\s+/g, '');
+}
+
+export function isMobilePhone(raw: string): boolean {
+ return MOBILE_PHONE_RE.test(normalizeContactPhone(raw));
+}
+
+export function isLandlinePhone(raw: string): boolean {
+ return LANDLINE_PHONE_RE.test(normalizeContactPhone(raw));
+}
+
+/** 门店对外联系电话:手机号或座机 */
+export function isStoreContactPhone(raw: string): boolean {
+ const s = normalizeContactPhone(raw);
+ return isMobilePhone(s) || isLandlinePhone(s);
+}
+
+/** 拨号用:去掉空格和横线 */
+export function toDialablePhone(raw: string): string {
+ return String(raw ?? '').replace(/[\s-]/g, '');
+}
diff --git a/packages/shared-types/src/store-package.ts b/packages/shared-types/src/store-package.ts
index abb36e3..24d075c 100644
--- a/packages/shared-types/src/store-package.ts
+++ b/packages/shared-types/src/store-package.ts
@@ -105,6 +105,10 @@ export interface StorePackageAuditAction {
rejectReason?: string;
}
+export interface StorePackageAuditSummaryDto {
+ pendingCount: number;
+}
+
export interface CreatePackageDisputeRequest {
storeId: string;
remark?: string;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 07b6153..ceed905 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@ant-design/icons':
specifier: ^5.5.1
version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@dukang/domain':
+ specifier: workspace:*
+ version: link:../../packages/domain
'@dukang/shared-types':
specifier: workspace:*
version: link:../../packages/shared-types
@@ -78,6 +81,9 @@ importers:
'@dukang/client-logging':
specifier: workspace:*
version: link:../../packages/client-logging
+ '@dukang/domain':
+ specifier: workspace:*
+ version: link:../../packages/domain
'@dukang/shared-types':
specifier: workspace:*
version: link:../../packages/shared-types
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 40f7ebb..a3028d9 100644
--- a/server/dukang-api/src/modules/ops/admin-stores.service.ts
+++ b/server/dukang-api/src/modules/ops/admin-stores.service.ts
@@ -1,6 +1,6 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
-import { validateBusinessHours } from '@dukang/domain';
+import { isMobilePhone, isStoreContactPhone, STORE_CONTACT_PHONE_HINT, validateBusinessHours } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { mapStoreCompat } from '../../common/compat/v31-compat';
@@ -277,14 +277,14 @@ export class AdminStoresService {
if (dto.phone !== undefined) {
const normalizedPhone = dto.phone.trim();
- if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
+ if (!isMobilePhone(normalizedPhone)) {
throw new BadRequestException('请输入正确的登录手机号');
}
}
if (dto.contactPhone !== undefined) {
const contact = dto.contactPhone.trim();
- if (contact && !/^1[3-9]\d{9}$/.test(contact)) {
- throw new BadRequestException('请输入正确的联系电话');
+ if (contact && !isStoreContactPhone(contact)) {
+ throw new BadRequestException(STORE_CONTACT_PHONE_HINT);
}
}
@@ -505,7 +505,7 @@ export class AdminStoresService {
async createStore(dto: CreateStoreDto) {
const normalizedPhone = dto.phone.trim();
- if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
+ if (!isMobilePhone(normalizedPhone)) {
throw new BadRequestException('请输入正确的手机号码');
}
const existingAccount = await this.prisma.storeAccount.findUnique({
@@ -576,8 +576,8 @@ export class AdminStoresService {
: await this.testWhitelist.isPhoneInWhitelist(normalizedPhone);
const contactPhoneRaw = dto.contactPhone?.trim() || normalizedPhone;
- if (!/^1[3-9]\d{9}$/.test(contactPhoneRaw)) {
- throw new BadRequestException('请输入正确的联系电话');
+ if (!isStoreContactPhone(contactPhoneRaw)) {
+ throw new BadRequestException(STORE_CONTACT_PHONE_HINT);
}
const store = await this.prisma.store.create({
diff --git a/server/dukang-api/src/modules/store/store-package.controller.ts b/server/dukang-api/src/modules/store/store-package.controller.ts
index 6fe1689..2cb62cd 100644
--- a/server/dukang-api/src/modules/store/store-package.controller.ts
+++ b/server/dukang-api/src/modules/store/store-package.controller.ts
@@ -70,6 +70,11 @@ export class AdminStorePackageController {
export class AdminStorePackageAuditController {
constructor(private readonly packages: StorePackageService) {}
+ @Get('summary')
+ summary() {
+ return this.packages.adminAuditSummary();
+ }
+
@Get()
list(
@Query('status') status?: string,
diff --git a/server/dukang-api/src/modules/store/store-package.service.ts b/server/dukang-api/src/modules/store/store-package.service.ts
index 13192ff..c72b92f 100644
--- a/server/dukang-api/src/modules/store/store-package.service.ts
+++ b/server/dukang-api/src/modules/store/store-package.service.ts
@@ -376,4 +376,11 @@ export class StorePackageService {
});
return serializeBigInt({ id: requestId.toString(), status: 'APPROVED' });
}
+
+ async adminAuditSummary() {
+ const pendingCount = await this.prisma.storePackageChangeRequest.count({
+ where: { status: 'PENDING' },
+ });
+ return { pendingCount };
+ }
}
diff --git a/server/dukang-api/src/modules/store/store.service.ts b/server/dukang-api/src/modules/store/store.service.ts
index 748712d..63c6e03 100644
--- a/server/dukang-api/src/modules/store/store.service.ts
+++ b/server/dukang-api/src/modules/store/store.service.ts
@@ -7,7 +7,7 @@ import {
import { loadAppConfig, ClientApp, SmsScene } from '@dukang/shared-types';
import { PARTNER_STAFF_ROLE_LABELS, PartnerStaffRole, type PartnerLeaderboardPeriod } from '@dukang/shared-types';
import { normalizeStorePackageImageUrls } from '@dukang/shared-types';
-import { validateBusinessHours } from '@dukang/domain';
+import { isStoreContactPhone, STORE_CONTACT_PHONE_HINT, validateBusinessHours } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { mapStoreCompat } from '../../common/compat/v31-compat';
@@ -371,8 +371,8 @@ export class StoreService {
body.contactPhone != null && String(body.contactPhone).trim()
? String(body.contactPhone).trim()
: normalizedPhone;
- if (!/^1[3-9]\d{9}$/.test(contactPhoneRaw)) {
- throw new BadRequestException('联系电话须为11位手机号');
+ if (!isStoreContactPhone(contactPhoneRaw)) {
+ throw new BadRequestException(STORE_CONTACT_PHONE_HINT);
}
const city = await this.resolvePartnerCity(partnerAccountId, body.cityId);
@@ -653,8 +653,8 @@ export class StoreService {
body.longitude !== undefined ? parseOptionalCoord(body.longitude, 'lng') : undefined;
if (name !== undefined && !name) throw new BadRequestException('请填写门店名称');
- if (contactPhone !== undefined && !/^1[3-9]\d{9}$/.test(contactPhone)) {
- throw new BadRequestException('联系电话须为11位手机号');
+ if (contactPhone !== undefined && !isStoreContactPhone(contactPhone)) {
+ throw new BadRequestException(STORE_CONTACT_PHONE_HINT);
}
if (address !== undefined && !address) throw new BadRequestException('请填写详细地址');
if (introRaw && (introRaw.length < 2 || introRaw.length > 500)) {