webadmin去掉新建门店的短信验证
This commit is contained in:
@@ -8,7 +8,6 @@ export type StoreCreateForm = {
|
|||||||
districtCode?: string;
|
districtCode?: string;
|
||||||
name: string;
|
name: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
smsCode: string;
|
|
||||||
address: string;
|
address: string;
|
||||||
intro?: string;
|
intro?: string;
|
||||||
coverUrl?: string;
|
coverUrl?: string;
|
||||||
@@ -23,7 +22,7 @@ const PHONE_RE = /^1\d{10}$/;
|
|||||||
const BANK_RE = /^\d{16,19}$/;
|
const BANK_RE = /^\d{16,19}$/;
|
||||||
|
|
||||||
export function validateStoreCreateStep1(
|
export function validateStoreCreateStep1(
|
||||||
form: Pick<StoreCreateForm, 'partnerId' | 'cityId' | 'regionCodes' | 'name' | 'phone' | 'smsCode' | 'address' | 'intro'>,
|
form: Pick<StoreCreateForm, 'partnerId' | 'cityId' | 'regionCodes' | 'name' | 'phone' | 'address' | 'intro'>,
|
||||||
): string | null {
|
): string | null {
|
||||||
if (!form.partnerId) return '请选择开城合伙人';
|
if (!form.partnerId) return '请选择开城合伙人';
|
||||||
if (!form.regionCodes || form.regionCodes.length < 3) return '请选择省 / 市 / 区县';
|
if (!form.regionCodes || form.regionCodes.length < 3) return '请选择省 / 市 / 区县';
|
||||||
@@ -31,7 +30,6 @@ export function validateStoreCreateStep1(
|
|||||||
if (!form.name?.trim()) return '请填写门店名称';
|
if (!form.name?.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.smsCode?.trim()) return '请填写短信验证码';
|
|
||||||
if (!form.address?.trim()) return '请填写详细地址';
|
if (!form.address?.trim()) return '请填写详细地址';
|
||||||
if (form.intro?.trim()) {
|
if (form.intro?.trim()) {
|
||||||
const len = form.intro.trim().length;
|
const len = form.intro.trim().length;
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export default function StoresPage() {
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [createStep, setCreateStep] = useState(0);
|
const [createStep, setCreateStep] = useState(0);
|
||||||
const [createError, setCreateError] = useState('');
|
const [createError, setCreateError] = useState('');
|
||||||
const [smsCooldown, setSmsCooldown] = useState(0);
|
|
||||||
const [partners, setPartners] = useState<PartnerOption[]>([]);
|
const [partners, setPartners] = useState<PartnerOption[]>([]);
|
||||||
const [cities, setCities] = useState<CityOption[]>([]);
|
const [cities, setCities] = useState<CityOption[]>([]);
|
||||||
const [optionsLoading, setOptionsLoading] = useState(false);
|
const [optionsLoading, setOptionsLoading] = useState(false);
|
||||||
@@ -141,38 +140,9 @@ export default function StoresPage() {
|
|||||||
setCreateOpen(false);
|
setCreateOpen(false);
|
||||||
setCreateStep(0);
|
setCreateStep(0);
|
||||||
setCreateError('');
|
setCreateError('');
|
||||||
setSmsCooldown(0);
|
|
||||||
createForm.resetFields();
|
createForm.resetFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateSms() {
|
|
||||||
const phone = String(createForm.getFieldValue('phone') ?? '').trim();
|
|
||||||
if (!/^1\d{10}$/.test(phone)) {
|
|
||||||
message.error('请先填写正确的11位门店手机号');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (smsCooldown > 0) return;
|
|
||||||
try {
|
|
||||||
await request('/admin/stores/phone/sms/send', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ phone }),
|
|
||||||
});
|
|
||||||
message.success('验证码已发送');
|
|
||||||
setSmsCooldown(60);
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setSmsCooldown((s) => {
|
|
||||||
if (s <= 1) {
|
|
||||||
clearInterval(timer);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return s - 1;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e instanceof Error ? e.message : '发送失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openCreateModal() {
|
function openCreateModal() {
|
||||||
void loadOptions();
|
void loadOptions();
|
||||||
createForm.setFieldsValue({
|
createForm.setFieldsValue({
|
||||||
@@ -192,7 +162,7 @@ export default function StoresPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await createForm.validateFields(['partnerId', 'regionCodes', 'cityId', 'name', 'phone', 'smsCode', 'address']);
|
await createForm.validateFields(['partnerId', 'regionCodes', 'cityId', 'name', 'phone', 'address']);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,7 +190,6 @@ 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(),
|
||||||
smsCode: values.smsCode.trim(),
|
|
||||||
district: values.district.trim(),
|
district: values.district.trim(),
|
||||||
address: values.address.trim(),
|
address: values.address.trim(),
|
||||||
intro: values.intro?.trim() || undefined,
|
intro: values.intro?.trim() || undefined,
|
||||||
@@ -239,7 +208,7 @@ export default function StoresPage() {
|
|||||||
if (e && typeof e === 'object' && 'errorFields' in e) {
|
if (e && typeof e === 'object' && 'errorFields' in e) {
|
||||||
const fields = e as { errorFields?: Array<{ name: string[] }> };
|
const fields = e as { errorFields?: Array<{ name: string[] }> };
|
||||||
const first = fields.errorFields?.[0]?.name?.[0];
|
const first = fields.errorFields?.[0]?.name?.[0];
|
||||||
if (first === 'partnerId' || first === 'cityId' || first === 'regionCodes' || first === 'name' || first === 'phone' || first === 'smsCode') {
|
if (first === 'partnerId' || first === 'cityId' || first === 'regionCodes' || first === 'name' || first === 'phone') {
|
||||||
setCreateStep(0);
|
setCreateStep(0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -409,16 +378,6 @@ export default function StoresPage() {
|
|||||||
<Form.Item name="phone" label="门店手机号(登录账号)" rules={[{ required: true, message: '请填写门店手机号' }]}>
|
<Form.Item name="phone" label="门店手机号(登录账号)" rules={[{ required: true, message: '请填写门店手机号' }]}>
|
||||||
<Input placeholder="11位手机号" />
|
<Input placeholder="11位手机号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="短信验证" required>
|
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
|
||||||
<Form.Item name="smsCode" noStyle rules={[{ required: true, message: '请填写验证码' }]}>
|
|
||||||
<Input placeholder="验证码" maxLength={6} />
|
|
||||||
</Form.Item>
|
|
||||||
<Button disabled={smsCooldown > 0} onClick={() => void sendCreateSms()}>
|
|
||||||
{smsCooldown > 0 ? `${smsCooldown}s` : '获取验证码'}
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="address" label="详细地址" rules={[{ required: true, message: '请填写详细地址' }]}>
|
<Form.Item name="address" label="详细地址" rules={[{ required: true, message: '请填写详细地址' }]}>
|
||||||
<Input.TextArea rows={2} placeholder="请输入详细门牌号" />
|
<Input.TextArea rows={2} placeholder="请输入详细门牌号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
if (!refreshed.accessToken) throw new Error('refresh failed');
|
if (!refreshed.accessToken) throw new Error('refresh failed');
|
||||||
|
|
||||||
console.log('4. STORE_ACCOUNT_OPEN occupied phone');
|
console.log('4. Admin create store rejects occupied phone');
|
||||||
await req('HQ_WEB', '/admin/auth/sms/send', {
|
await req('HQ_WEB', '/admin/auth/sms/send', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ phone: '13600000001', scene: 'HQ_LOGIN' }),
|
body: JSON.stringify({ phone: '13600000001', scene: 'HQ_LOGIN' }),
|
||||||
@@ -66,10 +66,17 @@ async function main() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ phone: '13600000001', code: MOCK_SMS_CODE }),
|
body: JSON.stringify({ phone: '13600000001', code: MOCK_SMS_CODE }),
|
||||||
});
|
});
|
||||||
const occupied = await expectFail('HQ_WEB', '/admin/stores/phone/sms/send', {
|
const occupied = await expectFail('HQ_WEB', '/admin/stores', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
token: admin.accessToken,
|
token: admin.accessToken,
|
||||||
body: JSON.stringify({ phone: '13910000001' }),
|
body: JSON.stringify({
|
||||||
|
partnerId: '1',
|
||||||
|
cityId: '1',
|
||||||
|
name: '重复手机号测试店',
|
||||||
|
phone: '13910000001',
|
||||||
|
district: '测试区',
|
||||||
|
address: '测试地址1号',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!occupied.includes('已绑定')) throw new Error(`unexpected: ${occupied}`);
|
if (!occupied.includes('已绑定')) throw new Error(`unexpected: ${occupied}`);
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ export class AdminStoresController {
|
|||||||
return this.service.listStores(query);
|
return this.service.listStores(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('phone/sms/send')
|
|
||||||
sendOpenSms(@Body() body: { phone: string }) {
|
|
||||||
return this.service.sendStoreOpenSms(body.phone);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
detail(@Param('id') id: string) {
|
detail(@Param('id') id: string) {
|
||||||
return this.service.detailStore(BigInt(id));
|
return this.service.detailStore(BigInt(id));
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { ClientApp, SmsScene } from '@dukang/shared-types';
|
|
||||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||||
import { mapStoreCompat } from '../../common/compat/v31-compat';
|
import { mapStoreCompat } from '../../common/compat/v31-compat';
|
||||||
import { AuthService } from '../iam/auth.service';
|
|
||||||
import type { AdminStoreAccountsQueryDto, AdminStoreMediaQueryDto, AdminStoresQueryDto } from './dto/admin-query.dto';
|
import type { AdminStoreAccountsQueryDto, AdminStoreMediaQueryDto, AdminStoresQueryDto } from './dto/admin-query.dto';
|
||||||
import type {
|
import type {
|
||||||
CreateStoreAccountDto,
|
CreateStoreAccountDto,
|
||||||
@@ -18,16 +16,7 @@ import type {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminStoresService {
|
export class AdminStoresService {
|
||||||
constructor(
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly authService: AuthService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async sendStoreOpenSms(phone: string) {
|
|
||||||
return this.authService.sendSms(phone, SmsScene.STORE_ACCOUNT_OPEN, {
|
|
||||||
clientApp: ClientApp.HQ_WEB,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async listStores(query: AdminStoresQueryDto) {
|
async listStores(query: AdminStoresQueryDto) {
|
||||||
const page = query.page ?? 1;
|
const page = query.page ?? 1;
|
||||||
@@ -168,7 +157,6 @@ export class AdminStoresService {
|
|||||||
if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
|
if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
|
||||||
throw new BadRequestException('请输入正确的手机号码');
|
throw new BadRequestException('请输入正确的手机号码');
|
||||||
}
|
}
|
||||||
await this.authService.verifySmsCode(normalizedPhone, dto.smsCode, SmsScene.STORE_ACCOUNT_OPEN);
|
|
||||||
const existingAccount = await this.prisma.storeAccount.findUnique({
|
const existingAccount = await this.prisma.storeAccount.findUnique({
|
||||||
where: { phone: normalizedPhone },
|
where: { phone: normalizedPhone },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ export class CreateStoreDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
smsCode: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
categoryId?: string;
|
categoryId?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user