|
@@ -22,7 +22,7 @@ export class OperationsUsersComponent implements OnInit, OnDestroy {
|
|
|
readonly query = signal(''); readonly status = signal('active'); readonly learningStatus = signal('studying'); readonly risk = signal('all');
|
|
readonly query = signal(''); readonly status = signal('active'); readonly learningStatus = signal('studying'); readonly risk = signal('all');
|
|
|
readonly parentUserId = signal(0); readonly registeredFrom = signal(''); readonly registeredTo = signal(''); readonly sortValue = signal('registeredAt:desc');
|
|
readonly parentUserId = signal(0); readonly registeredFrom = signal(''); readonly registeredTo = signal(''); readonly sortValue = signal('registeredAt:desc');
|
|
|
readonly loading = signal(true); readonly searching = signal(false); readonly refreshing = signal(false); readonly saving = signal(false); readonly exporting = signal(false); readonly error = signal(''); readonly message = signal(''); readonly lastRefreshedAt = signal('');
|
|
readonly loading = signal(true); readonly searching = signal(false); readonly refreshing = signal(false); readonly saving = signal(false); readonly exporting = signal(false); readonly error = signal(''); readonly message = signal(''); readonly lastRefreshedAt = signal('');
|
|
|
- readonly createOpen = signal(false); readonly createName = signal(''); readonly createNickname = signal(''); readonly createMobile = signal(''); readonly createGender = signal(''); readonly createGrade = signal(''); readonly createPassword = signal(''); readonly createParentUserId = signal(0); readonly createAgentSearch = signal(''); readonly createReason = signal('新增会员');
|
|
|
|
|
|
|
+ readonly createOpen = signal(false); readonly createName = signal(''); readonly createNickname = signal(''); readonly createMobile = signal(''); readonly createGender = signal(''); readonly createGrade = signal(''); readonly createParentUserId = signal(0); readonly createAgentSearch = signal(''); readonly createReason = signal('新增会员');
|
|
|
readonly importOpen = signal(false); readonly importFileName = signal(''); readonly importRows = signal<Record<string, unknown>[]>([]); readonly importPreview = signal<MemberImportPreview | null>(null); readonly importReason = signal('批量导入会员'); readonly importing = signal(false); readonly importIdempotencyKey = signal('');
|
|
readonly importOpen = signal(false); readonly importFileName = signal(''); readonly importRows = signal<Record<string, unknown>[]>([]); readonly importPreview = signal<MemberImportPreview | null>(null); readonly importReason = signal('批量导入会员'); readonly importing = signal(false); readonly importIdempotencyKey = signal('');
|
|
|
readonly icons = { ChevronLeft, ChevronRight, Download, Eye, FileSpreadsheet, LoaderCircle, Plus, RefreshCw, Search, Upload, UserPlus, Users, X };
|
|
readonly icons = { ChevronLeft, ChevronRight, Download, Eye, FileSpreadsheet, LoaderCircle, Plus, RefreshCw, Search, Upload, UserPlus, Users, X };
|
|
|
private readonly router = inject(Router); private readonly operations = inject(OperationsService); private readonly sessions = inject(AdminSessionService); private readonly liveRefresh = inject(OperationsLiveRefreshService); private stopLiveRefresh?: () => void;
|
|
private readonly router = inject(Router); private readonly operations = inject(OperationsService); private readonly sessions = inject(AdminSessionService); private readonly liveRefresh = inject(OperationsLiveRefreshService); private stopLiveRefresh?: () => void;
|
|
@@ -61,7 +61,7 @@ export class OperationsUsersComponent implements OnInit, OnDestroy {
|
|
|
pageCount(): number { const value = this.page(); return Math.max(1, Math.ceil((value?.total ?? 0) / (value?.pageSize ?? 20))); }
|
|
pageCount(): number { const value = this.page(); return Math.max(1, Math.ceil((value?.total ?? 0) / (value?.pageSize ?? 20))); }
|
|
|
selectCreateAgent(agent: MemberAgentOption): void { this.createParentUserId.set(agent.userId); this.createAgentSearch.set(agent.displayName); }
|
|
selectCreateAgent(agent: MemberAgentOption): void { this.createParentUserId.set(agent.userId); this.createAgentSearch.set(agent.displayName); }
|
|
|
clearCreateAgent(): void { this.createParentUserId.set(0); this.createAgentSearch.set(''); }
|
|
clearCreateAgent(): void { this.createParentUserId.set(0); this.createAgentSearch.set(''); }
|
|
|
- openCreate(): void { this.createName.set(''); this.createNickname.set(''); this.createMobile.set(''); this.createGender.set(''); this.createGrade.set(''); this.createPassword.set(''); this.clearCreateAgent(); this.createReason.set('新增会员'); this.error.set(''); this.createOpen.set(true); }
|
|
|
|
|
|
|
+ openCreate(): void { this.createName.set(''); this.createNickname.set(''); this.createMobile.set(''); this.createGender.set(''); this.createGrade.set(''); this.clearCreateAgent(); this.createReason.set('新增会员'); this.error.set(''); this.createOpen.set(true); }
|
|
|
|
|
|
|
|
openImport(): void {
|
|
openImport(): void {
|
|
|
this.importFileName.set(''); this.importRows.set([]); this.importPreview.set(null); this.importReason.set('批量导入会员'); this.importIdempotencyKey.set(''); this.error.set(''); this.importOpen.set(true);
|
|
this.importFileName.set(''); this.importRows.set([]); this.importPreview.set(null); this.importReason.set('批量导入会员'); this.importIdempotencyKey.set(''); this.error.set(''); this.importOpen.set(true);
|
|
@@ -77,7 +77,7 @@ export class OperationsUsersComponent implements OnInit, OnDestroy {
|
|
|
const sheet = utils.aoa_to_sheet([['姓名', '昵称', '手机号', '性别', '年级', '邮箱', '门店ID', '所属门店', '临时密码']]);
|
|
const sheet = utils.aoa_to_sheet([['姓名', '昵称', '手机号', '性别', '年级', '邮箱', '门店ID', '所属门店', '临时密码']]);
|
|
|
sheet['!cols'] = [{ wch: 14 }, { wch: 14 }, { wch: 20 }, { wch: 10 }, { wch: 14 }, { wch: 22 }, { wch: 20 }, { wch: 22 }, { wch: 18 }];
|
|
sheet['!cols'] = [{ wch: 14 }, { wch: 14 }, { wch: 20 }, { wch: 10 }, { wch: 14 }, { wch: 22 }, { wch: 20 }, { wch: 22 }, { wch: 18 }];
|
|
|
const instructions = utils.aoa_to_sheet([
|
|
const instructions = utils.aoa_to_sheet([
|
|
|
- ['字段', '填写要求'], ['姓名', '必填,最长 100 字'], ['昵称', '可选,不填时使用姓名'], ['手机号', '必填,11 位;同时作为登录账号'], ['性别', '可选:男、女或其他'], ['年级', '可选,最长 50 字'], ['邮箱', '可选,需为有效邮箱'], ['门店ID / 所属门店', '二选一;名称重复时必须填写门店 ID'], ['临时密码', '必填,8 至 72 位;预检批次与日志均不保存明文'], ['单次上限', '200 人;请勿改动“会员导入”工作表的表头'],
|
|
|
|
|
|
|
+ ['字段', '填写要求'], ['姓名', '必填,最长 100 字'], ['昵称', '可选,不填时使用姓名'], ['手机号', '必填,11 位;同时作为登录账号'], ['性别', '可选:男、女或其他'], ['年级', '可选,最长 50 字'], ['邮箱', '可选,需为有效邮箱'], ['门店ID / 所属门店', '二选一;名称重复时必须填写门店 ID'], ['临时密码', '统一使用 12345678,无需填写;学生不能自行修改'], ['单次上限', '200 人;请勿改动“会员导入”工作表的表头'],
|
|
|
]); instructions['!cols'] = [{ wch: 24 }, { wch: 58 }];
|
|
]); instructions['!cols'] = [{ wch: 24 }, { wch: 58 }];
|
|
|
const workbook = utils.book_new(); utils.book_append_sheet(workbook, sheet, '会员导入'); utils.book_append_sheet(workbook, instructions, '填写说明'); writeFile(workbook, '小树陪练会员导入模板.xlsx');
|
|
const workbook = utils.book_new(); utils.book_append_sheet(workbook, sheet, '会员导入'); utils.book_append_sheet(workbook, instructions, '填写说明'); writeFile(workbook, '小树陪练会员导入模板.xlsx');
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -119,9 +119,9 @@ export class OperationsUsersComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
|
|
async createMember(): Promise<void> {
|
|
async createMember(): Promise<void> {
|
|
|
if (this.saving()) return;
|
|
if (this.saving()) return;
|
|
|
- if (!this.createName().trim() || !/^1\d{10}$/.test(this.createMobile()) || this.createPassword().length < 8 || this.createReason().trim().length < 2) { this.error.set('请填写姓名、有效手机号、至少 8 位临时密码和操作原因'); return; }
|
|
|
|
|
|
|
+ if (!this.createName().trim() || !/^1\d{10}$/.test(this.createMobile()) || this.createReason().trim().length < 2) { this.error.set('请填写姓名、有效手机号和操作原因'); return; }
|
|
|
this.saving.set(true); this.error.set('');
|
|
this.saving.set(true); this.error.set('');
|
|
|
- try { const created=await this.operations.mutate<{objectId:string}>('ops/users/create', '', this.createReason(), { displayName: this.createName(), nickname: this.createNickname() || this.createName(), mobile: this.createMobile(), username: this.createMobile(), gender: this.createGender(), grade: this.createGrade(), parentUserId: this.createParentUserId(), password: this.createPassword() }); this.createOpen.set(false); this.message.set('会员已新增,请继续充值课时'); await this.router.navigate(['/admin/members',created.objectId],{queryParams:{tab:'balances'}}); }
|
|
|
|
|
|
|
+ try { const created=await this.operations.mutate<{objectId:string}>('ops/users/create', '', this.createReason(), { displayName: this.createName(), nickname: this.createNickname() || this.createName(), mobile: this.createMobile(), username: this.createMobile(), gender: this.createGender(), grade: this.createGrade(), parentUserId: this.createParentUserId() }); this.createOpen.set(false); this.message.set('会员已新增,请继续充值课时'); await this.router.navigate(['/admin/members',created.objectId],{queryParams:{tab:'balances'}}); }
|
|
|
catch (error) { this.error.set(error instanceof Error ? error.message : '新增会员失败'); }
|
|
catch (error) { this.error.set(error instanceof Error ? error.message : '新增会员失败'); }
|
|
|
finally { this.saving.set(false); }
|
|
finally { this.saving.set(false); }
|
|
|
}
|
|
}
|