|
|
@@ -0,0 +1,165 @@
|
|
|
+import { CommonModule } from '@angular/common';
|
|
|
+import { ChangeDetectionStrategy, Component, OnInit, inject, signal } from '@angular/core';
|
|
|
+import { FormsModule } from '@angular/forms';
|
|
|
+import { Building2, ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw, Search, Trash2, Users, X } from 'lucide-angular';
|
|
|
+import { LucideAngularModule } from 'lucide-angular';
|
|
|
+import { StorePage, StoreSummary } from '../admin.models';
|
|
|
+import { OperationsService } from '../operations.service';
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'app-operations-stores',
|
|
|
+ standalone: true,
|
|
|
+ imports: [CommonModule, FormsModule, LucideAngularModule],
|
|
|
+ templateUrl: './operations-stores.component.html',
|
|
|
+ styleUrls: ['./operations-professional.scss', './operations-stores.component.scss'],
|
|
|
+ changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
+})
|
|
|
+export class OperationsStoresComponent implements OnInit {
|
|
|
+ readonly page = signal<StorePage | null>(null);
|
|
|
+ readonly query = signal('');
|
|
|
+ readonly status = signal('all');
|
|
|
+ readonly loading = signal(true);
|
|
|
+ readonly saving = signal(false);
|
|
|
+ readonly error = signal('');
|
|
|
+ readonly message = signal('');
|
|
|
+ readonly drawerOpen = signal(false);
|
|
|
+ readonly editing = signal<StoreSummary | null>(null);
|
|
|
+ readonly deleteArmed = signal(false);
|
|
|
+
|
|
|
+ readonly username = signal('');
|
|
|
+ readonly password = signal('');
|
|
|
+ readonly storeName = signal('');
|
|
|
+ readonly contactName = signal('');
|
|
|
+ readonly mobile = signal('');
|
|
|
+ readonly parentStoreId = signal(0);
|
|
|
+ readonly disabled = signal(false);
|
|
|
+ readonly reason = signal('');
|
|
|
+
|
|
|
+ readonly icons = { Building2, ChevronLeft, ChevronRight, Pencil, Plus, RefreshCw, Search, Trash2, Users, X };
|
|
|
+ private readonly operations = inject(OperationsService);
|
|
|
+
|
|
|
+ ngOnInit(): void { void this.load(1); }
|
|
|
+
|
|
|
+ async load(page = 1): Promise<void> {
|
|
|
+ this.loading.set(true);
|
|
|
+ this.error.set('');
|
|
|
+ try {
|
|
|
+ this.page.set(await this.operations.stores(page, 20, this.query(), { status: this.status() }));
|
|
|
+ } catch (error) {
|
|
|
+ this.error.set(error instanceof Error ? error.message : '门店数据加载失败');
|
|
|
+ } finally {
|
|
|
+ this.loading.set(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ reset(): void {
|
|
|
+ this.query.set('');
|
|
|
+ this.status.set('all');
|
|
|
+ void this.load(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ pageCount(): number { return Math.max(1, Math.ceil((this.page()?.total || 0) / (this.page()?.pageSize || 20))); }
|
|
|
+
|
|
|
+ availableParents() {
|
|
|
+ const ownId = this.editing()?.userId || 0;
|
|
|
+ return (this.page()?.parents || []).filter((store) => store.userId !== ownId);
|
|
|
+ }
|
|
|
+
|
|
|
+ openCreate(): void {
|
|
|
+ this.editing.set(null);
|
|
|
+ this.username.set('');
|
|
|
+ this.password.set('');
|
|
|
+ this.storeName.set('');
|
|
|
+ this.contactName.set('');
|
|
|
+ this.mobile.set('');
|
|
|
+ this.parentStoreId.set(0);
|
|
|
+ this.disabled.set(false);
|
|
|
+ this.reason.set('新增门店');
|
|
|
+ this.deleteArmed.set(false);
|
|
|
+ this.error.set('');
|
|
|
+ this.drawerOpen.set(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ openEdit(row: StoreSummary, deleteMode = false): void {
|
|
|
+ this.editing.set(row);
|
|
|
+ this.username.set(row.username);
|
|
|
+ this.password.set('');
|
|
|
+ this.storeName.set(row.storeName);
|
|
|
+ this.contactName.set(row.contactName || '');
|
|
|
+ this.mobile.set(row.mobile || '');
|
|
|
+ this.parentStoreId.set(row.parentStoreId || 0);
|
|
|
+ this.disabled.set(row.status === 'disabled');
|
|
|
+ this.reason.set('');
|
|
|
+ this.deleteArmed.set(deleteMode);
|
|
|
+ this.error.set('');
|
|
|
+ this.drawerOpen.set(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ closeDrawer(): void {
|
|
|
+ this.drawerOpen.set(false);
|
|
|
+ this.deleteArmed.set(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ async save(): Promise<void> {
|
|
|
+ const editing = this.editing();
|
|
|
+ if (!this.username().trim() || !this.storeName().trim() || !this.reason().trim()) {
|
|
|
+ this.error.set('登录账号、门店名称和操作原因必填');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!editing && !this.password()) {
|
|
|
+ this.error.set('新增门店必须设置初始密码');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.saving.set(true);
|
|
|
+ this.error.set('');
|
|
|
+ try {
|
|
|
+ await this.operations.mutate(
|
|
|
+ editing ? 'ops/stores/update' : 'ops/stores/create',
|
|
|
+ editing?.objectId || '',
|
|
|
+ this.reason(),
|
|
|
+ {
|
|
|
+ username: this.username().trim(),
|
|
|
+ password: this.password(),
|
|
|
+ storeName: this.storeName().trim(),
|
|
|
+ contactName: this.contactName().trim(),
|
|
|
+ mobile: this.mobile().trim(),
|
|
|
+ parentStoreId: this.parentStoreId(),
|
|
|
+ disabled: this.disabled(),
|
|
|
+ },
|
|
|
+ editing?.updatedAt || '',
|
|
|
+ );
|
|
|
+ this.message.set(editing ? '门店资料已更新' : '门店已新增');
|
|
|
+ this.closeDrawer();
|
|
|
+ await this.load(editing ? this.page()?.page || 1 : 1);
|
|
|
+ } catch (error) {
|
|
|
+ this.error.set(error instanceof Error ? error.message : '门店资料保存失败');
|
|
|
+ } finally {
|
|
|
+ this.saving.set(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async remove(): Promise<void> {
|
|
|
+ const editing = this.editing();
|
|
|
+ if (!editing) return;
|
|
|
+ if (!this.reason().trim()) {
|
|
|
+ this.error.set('删除前必须填写操作原因');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!this.deleteArmed()) {
|
|
|
+ this.deleteArmed.set(true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.saving.set(true);
|
|
|
+ this.error.set('');
|
|
|
+ try {
|
|
|
+ await this.operations.mutate('ops/stores/delete', editing.objectId, this.reason(), {}, editing.updatedAt);
|
|
|
+ this.message.set('门店已删除,历史业务数据仍保留');
|
|
|
+ this.closeDrawer();
|
|
|
+ await this.load(1);
|
|
|
+ } catch (error) {
|
|
|
+ this.error.set(error instanceof Error ? error.message : '门店删除失败');
|
|
|
+ } finally {
|
|
|
+ this.saving.set(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|