snihwxf 3 месяцев назад
Родитель
Сommit
3948eb45b2
34 измененных файлов с 1972 добавлено и 6 удалено
  1. 3 2
      apps/web-baicai/src/adapter/vxe-table.ts
  2. 1 0
      apps/web-baicai/src/api/index.ts
  3. 50 0
      apps/web-baicai/src/api/shop/goods/brand.ts
  4. 49 0
      apps/web-baicai/src/api/shop/goods/category.ts
  5. 3 0
      apps/web-baicai/src/api/shop/goods/index.ts
  6. 37 0
      apps/web-baicai/src/api/shop/goods/params.ts
  7. 2 0
      apps/web-baicai/src/api/shop/index.ts
  8. 50 0
      apps/web-baicai/src/api/shop/user/grade.ts
  9. 2 0
      apps/web-baicai/src/api/shop/user/index.ts
  10. 52 0
      apps/web-baicai/src/api/shop/user/info.ts
  11. 1 0
      apps/web-baicai/src/api/system/enum.ts
  12. 3 0
      apps/web-baicai/src/api/system/file.ts
  13. 3 0
      apps/web-baicai/src/api/system/generate/generate.ts
  14. 11 1
      apps/web-baicai/src/components/form/components/bc-cropper.vue
  15. 13 1
      apps/web-baicai/src/components/icon/icon.vue
  16. 3 1
      apps/web-baicai/src/components/image-cropper/src/image-cropper.vue
  17. 73 0
      apps/web-baicai/src/views/shop/goods/brand/components/edit.vue
  18. 113 0
      apps/web-baicai/src/views/shop/goods/brand/data.config.ts
  19. 108 0
      apps/web-baicai/src/views/shop/goods/brand/index.vue
  20. 73 0
      apps/web-baicai/src/views/shop/goods/category/components/edit.vue
  21. 125 0
      apps/web-baicai/src/views/shop/goods/category/data.config.ts
  22. 115 0
      apps/web-baicai/src/views/shop/goods/category/index.vue
  23. 73 0
      apps/web-baicai/src/views/shop/goods/params/components/edit.vue
  24. 121 0
      apps/web-baicai/src/views/shop/goods/params/data.config.ts
  25. 96 0
      apps/web-baicai/src/views/shop/goods/params/index.vue
  26. 73 0
      apps/web-baicai/src/views/shop/user/grade/components/edit.vue
  27. 154 0
      apps/web-baicai/src/views/shop/user/grade/data.config.ts
  28. 108 0
      apps/web-baicai/src/views/shop/user/grade/index.vue
  29. 88 0
      apps/web-baicai/src/views/shop/user/info/components/edit.vue
  30. 217 0
      apps/web-baicai/src/views/shop/user/info/data.config.ts
  31. 132 0
      apps/web-baicai/src/views/shop/user/info/index.vue
  32. 9 0
      apps/web-baicai/src/views/system/design/generate/info/data.config.ts
  33. 9 0
      apps/web-baicai/src/views/system/design/generate/info/index.vue
  34. 2 1
      apps/web-baicai/src/views/system/user/data.config.ts

+ 3 - 2
apps/web-baicai/src/adapter/vxe-table.ts

@@ -262,7 +262,7 @@ setupVbenVxeTable({
                   'div',
                   { class: 'truncate' },
                   $t('ui.actionMessage.deleteConfirm', [
-                    row[attrs?.nameField || 'name'],
+                    get(row, attrs?.nameField || 'name'),
                   ]),
                 ),
             },
@@ -291,13 +291,14 @@ setupVbenVxeTable({
     vxeUI.renderer.add('CellAction', {
       renderTableDefault({ attrs, options, props }, { column, row }) {
         const defaultProps = { size: 'small', type: 'text', ...props };
+        const fieldValue = get(row, attrs?.nameField || 'name');
         const presets: Recordable<Recordable<any>> = {
           delete: {
             danger: true,
             label: $t('common.delete'),
             confirm: {
               title: '删除提示',
-              content: `确定要删除记录 [${row[attrs?.nameField || 'name']}] 吗?`,
+              content: `确定要删除记录 [${fieldValue}] 吗?`,
             },
           },
           edit: {

+ 1 - 0
apps/web-baicai/src/api/index.ts

@@ -2,4 +2,5 @@ export * from './cms';
 export * from './core';
 export * from './examples';
 export * from './plugins';
+export * from './shop';
 export * from './system';

+ 50 - 0
apps/web-baicai/src/api/shop/goods/brand.ts

@@ -0,0 +1,50 @@
+import type {
+  BasicFetchResult,
+  BasicOptionResult,
+  BasicPageParams,
+  StatusParams,
+} from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace BrandApi {
+  export interface PageParams extends BasicPageParams {
+    title?: string;
+  }
+
+  export interface BasicRecordItem {
+    title: string;
+    status: number;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type PageResult = BasicFetchResult<RecordItem>;
+
+  export const getPage = (params: PageParams) =>
+    requestClient.get<PageResult>('/shop/brand/page', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/shop/brand/entity', {
+      params: { id },
+    });
+
+  export const getOptions = (id: number) =>
+    requestClient.get<BasicOptionResult[]>('/shop/brand/options', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/shop/brand', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/shop/brand', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/shop/brand', { data: { id } });
+
+  export const updateStatus = (data: StatusParams) =>
+    requestClient.put('/shop/brand/status', data);
+}

+ 49 - 0
apps/web-baicai/src/api/shop/goods/category.ts

@@ -0,0 +1,49 @@
+import type {
+  BasicFetchResult,
+  BasicOptionResult,
+  StatusParams,
+} from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace CategoryApi {
+  export interface PageParams {
+    name?: string;
+  }
+
+  export interface BasicRecordItem {
+    name: string;
+    status: number;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type PageResult = BasicFetchResult<RecordItem>;
+
+  export const getList = (params: PageParams) =>
+    requestClient.get<PageResult>('/shop/category/list', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/shop/category/entity', {
+      params: { id },
+    });
+
+  export const getTree = (id: number) =>
+    requestClient.get<BasicOptionResult[]>('/shop/category/tree-options', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/shop/category', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/shop/category', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/shop/category', { data: { id } });
+
+  export const updateStatus = (data: StatusParams) =>
+    requestClient.put('/shop/category/status', data);
+}

+ 3 - 0
apps/web-baicai/src/api/shop/goods/index.ts

@@ -0,0 +1,3 @@
+export * from './brand';
+export * from './category';
+export * from './params';

+ 37 - 0
apps/web-baicai/src/api/shop/goods/params.ts

@@ -0,0 +1,37 @@
+import type { BasicFetchResult, BasicPageParams } from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace ParamsApi {
+  export interface PageParams extends BasicPageParams {
+    name?: string;
+    ParamsType?: number;
+  }
+
+  export interface BasicRecordItem {
+    name: string;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type PageResult = BasicFetchResult<RecordItem>;
+
+  export const getPage = (params: PageParams) =>
+    requestClient.get<PageResult>('/shop/params/page', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/shop/params/entity', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/shop/params', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/shop/params', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/shop/params', { data: { id } });
+}

+ 2 - 0
apps/web-baicai/src/api/shop/index.ts

@@ -0,0 +1,2 @@
+export * from './goods';
+export * from './user';

+ 50 - 0
apps/web-baicai/src/api/shop/user/grade.ts

@@ -0,0 +1,50 @@
+import type {
+  BasicFetchResult,
+  BasicOptionResult,
+  BasicPageParams,
+  StatusParams,
+} from '#/api/model';
+
+import { requestClient } from '#/api/request';
+
+export namespace UserGradeApi {
+  export interface PageParams extends BasicPageParams {
+    title?: string;
+  }
+
+  export interface BasicRecordItem {
+    title: string;
+    status: number;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+  }
+
+  export type PageResult = BasicFetchResult<RecordItem>;
+
+  export const getPage = (params: PageParams) =>
+    requestClient.get<PageResult>('/shop/user/grade/page', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/shop/user/grade/entity', {
+      params: { id },
+    });
+
+  export const getOptions = (id: number) =>
+    requestClient.get<BasicOptionResult[]>('/shop/user/grade/options', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/shop/user/grade', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/shop/user/grade', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/shop/user/grade', { data: { id } });
+
+  export const updateStatus = (data: StatusParams) =>
+    requestClient.put('/shop/user/grade/status', data);
+}

+ 2 - 0
apps/web-baicai/src/api/shop/user/index.ts

@@ -0,0 +1,2 @@
+export * from './grade';
+export * from './info';

+ 52 - 0
apps/web-baicai/src/api/shop/user/info.ts

@@ -0,0 +1,52 @@
+import type {
+  BasicFetchResult,
+  BasicOptionResult,
+  StatusParams,
+} from '#/api/model';
+import type { UserApi } from '#/api/system';
+
+import { requestClient } from '#/api/request';
+
+export namespace ShopUserApi {
+  export interface PageParams extends UserApi.PageParams {
+    grade?: number;
+  }
+
+  export interface BasicRecordItem {
+    grade: number;
+    userId: number;
+  }
+
+  export interface RecordItem extends BasicRecordItem {
+    id: number;
+    status: number;
+    user: any;
+  }
+
+  export type PageResult = BasicFetchResult<RecordItem>;
+
+  export const getPage = (params: PageParams) =>
+    requestClient.get<PageResult>('/shop/user/page', { params });
+
+  export const getDetail = (id: number) =>
+    requestClient.get<RecordItem>('/shop/user/entity', {
+      params: { id },
+    });
+
+  export const getOptions = (id: number) =>
+    requestClient.get<BasicOptionResult[]>('/shop/user/options', {
+      params: { id },
+    });
+
+  export const addDetail = (data: BasicRecordItem) =>
+    requestClient.post('/shop/user', data);
+
+  export const editDetail = (data: RecordItem) =>
+    requestClient.put('/shop/user', data);
+
+  export const deleteDetail = (id: number) =>
+    requestClient.delete('/shop/user', { data: { id } });
+
+  export const updateStatus = (data: StatusParams) =>
+    requestClient.put('/shop/user/status', data);
+}

+ 1 - 0
apps/web-baicai/src/api/system/enum.ts

@@ -7,6 +7,7 @@ export namespace EnumApi {
     DataScope = 'DataScope',
     Gender = 'Gender',
     MenuType = 'MenuType',
+    ParamsType = 'ParamsType',
     PathType = 'PathType',
     PluginsType = 'PluginsType',
     Provider = 'Provider',

+ 3 - 0
apps/web-baicai/src/api/system/file.ts

@@ -48,6 +48,9 @@ export namespace FileApi {
   export const deleteDetail = (id: number) =>
     requestClient.delete('/file', { data: { id } });
 
+  export const deleteDetailByUrl = (url: string) =>
+    requestClient.delete('/file/url', { data: { code: url } });
+
   export const uploadBase64 = (data: BasicRecordItem) =>
     requestClient.post('/file/upload-base64', data);
 

+ 3 - 0
apps/web-baicai/src/api/system/generate/generate.ts

@@ -39,6 +39,9 @@ export namespace GenerateApi {
       params: { id },
     });
 
+  export const generate = (id: number) =>
+    requestClient.post('/generate/Generate', { id });
+
   export const addDetail = (data: BasicRecordItem) =>
     requestClient.post('/generate', data);
 

Разница между файлами не показана из-за своего большого размера
+ 11 - 1
apps/web-baicai/src/components/form/components/bc-cropper.vue


+ 13 - 1
apps/web-baicai/src/components/icon/icon.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import type { PropType } from 'vue';
+
 import { computed, h } from 'vue';
 
 import { IconifyIcon as VbenIcon } from '@vben/icons';
@@ -12,6 +14,10 @@ const props = defineProps({
     type: [String, Number],
     default: '16px',
   },
+  class: {
+    type: String as PropType<string>,
+    default: '',
+  },
 });
 const iconComp = computed(() => {
   return props.icon.startsWith('http')
@@ -31,7 +37,13 @@ const styles = computed(() => {
 
 <template>
   <component :is="iconComp" v-if="iconComp" :style="styles" />
-  <VbenIcon v-else :icon="props.icon" :style="styles" class="m-icon__" />
+  <VbenIcon
+    v-else
+    :icon="props.icon"
+    :style="styles"
+    class="m-icon__"
+    :class="props.class"
+  />
 </template>
 <style lang="less" scoped>
 .m-icon__ {

Разница между файлами не показана из-за своего большого размера
+ 3 - 1
apps/web-baicai/src/components/image-cropper/src/image-cropper.vue


+ 73 - 0
apps/web-baicai/src/views/shop/goods/brand/components/edit.vue

@@ -0,0 +1,73 @@
+<script lang="ts" setup>
+import { computed, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { BrandApi } from '#/api';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'UserGradeEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm(
+  useFormOptions({
+    schema: useSchema(),
+  }),
+);
+
+const [Modal, { close, setState, getData, lock, unlock }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  closeOnClickModal: false,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (!valid) return;
+      const values = await getValues();
+      lock();
+      const postParams = unref(modelRef);
+      Object.assign(postParams, values);
+      await (unref(isUpdate)
+        ? BrandApi.editDetail(postParams as BrandApi.RecordItem)
+        : BrandApi.addDetail(postParams as BrandApi.BasicRecordItem));
+      alert('操作成功');
+
+      emit('success');
+      close();
+    } finally {
+      unlock();
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+
+      if (unref(isUpdate)) {
+        const entity = await BrandApi.getDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑品牌' : '新增品牌'));
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form />
+  </Modal>
+</template>

+ 113 - 0
apps/web-baicai/src/views/shop/goods/brand/data.config.ts

@@ -0,0 +1,113 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+import type { BrandApi } from '#/api/shop';
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<BrandApi.RecordItem>,
+): VxeTableGridOptions<BrandApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'name',
+      title: '名称',
+    },
+    {
+      field: 'imageUrl',
+      title: 'LOGO',
+      width: 100,
+      cellRender: { name: 'CellImage' },
+    },
+    {
+      field: 'status',
+      title: '状态',
+      width: 82,
+      cellRender: { name: 'CellTag' },
+    },
+    {
+      align: 'right',
+      field: 'sort',
+      title: '排序',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'name',
+          nameTitle: '名称',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['brand:edit'],
+          },
+          {
+            code: 'delete',
+            auth: ['brand:delete'],
+          },
+          {
+            code: 'setStatus',
+            label: (row: BrandApi.RecordItem) => {
+              return row.status === 1 ? '禁用' : '启用';
+            },
+            auth: ['brand:setStatus'],
+          },
+        ],
+      },
+      field: 'operation',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      title: '操作',
+      width: 100,
+    },
+  ];
+}
+
+export const useSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'name',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'BcCropper',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'imageUrl',
+      label: 'LOGO',
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+  ];
+};

+ 108 - 0
apps/web-baicai/src/views/shop/goods/brand/index.vue

@@ -0,0 +1,108 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { BrandApi } from '#/api';
+
+import FormEdit from './components/edit.vue';
+import { useColumns, useSearchSchema } from './data.config';
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handelSuccess = () => {
+  reload();
+};
+const handleDelete = async (id: number) => {
+  await BrandApi.deleteDetail(id);
+  message.success('数据删除成功');
+  handelSuccess();
+};
+
+const handleUpdateStatus = async (record: any) => {
+  await BrandApi.updateStatus({
+    id: record.id,
+    status: record.status === 1 ? 2 : 1,
+  });
+  handelSuccess();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi
+    .setData({
+      isUpdate,
+      baseData: { id: record.id },
+    })
+    .open();
+};
+
+const handleActionClick = async ({
+  code,
+  row,
+}: OnActionClickParams<BrandApi.RecordItem>) => {
+  switch (code) {
+    case 'delete': {
+      await handleDelete(row.id);
+      break;
+    }
+    case 'edit': {
+      handleEdit(row, true);
+      break;
+    }
+    case 'setStatus': {
+      await handleUpdateStatus(row);
+      break;
+    }
+  }
+};
+
+const [Grid, { reload }] = useVbenVxeGrid(
+  useTableGridOptions({
+    formOptions: {
+      schema: useSearchSchema(),
+    },
+    gridOptions: {
+      columns: useColumns(handleActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async ({ page }, formValues) => {
+            return await BrandApi.getPage({
+              pageIndex: page.currentPage,
+              pageSize: page.pageSize,
+              ...formValues,
+            });
+          },
+        },
+      },
+    } as VxeTableGridOptions,
+  }),
+);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal @success="handelSuccess" />
+    <Grid>
+      <template #table-title>
+        <span class="border-l-primary border-l-8 border-solid pl-2">
+          品牌列表
+        </span>
+      </template>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'brand:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增品牌
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 73 - 0
apps/web-baicai/src/views/shop/goods/category/components/edit.vue

@@ -0,0 +1,73 @@
+<script lang="ts" setup>
+import { computed, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { CategoryApi } from '#/api';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'UserGradeEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm(
+  useFormOptions({
+    schema: useSchema(),
+  }),
+);
+
+const [Modal, { close, setState, getData, lock, unlock }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  closeOnClickModal: false,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (!valid) return;
+      const values = await getValues();
+      lock();
+      const postParams = unref(modelRef);
+      Object.assign(postParams, values);
+      await (unref(isUpdate)
+        ? CategoryApi.editDetail(postParams as CategoryApi.RecordItem)
+        : CategoryApi.addDetail(postParams as CategoryApi.BasicRecordItem));
+      alert('操作成功');
+
+      emit('success');
+      close();
+    } finally {
+      unlock();
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+
+      if (unref(isUpdate)) {
+        const entity = await CategoryApi.getDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑分类' : '新增分类'));
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form />
+  </Modal>
+</template>

+ 125 - 0
apps/web-baicai/src/views/shop/goods/category/data.config.ts

@@ -0,0 +1,125 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+
+import { CategoryApi } from '#/api/shop';
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<CategoryApi.RecordItem>,
+): VxeTableGridOptions<CategoryApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'name',
+      title: '名称',
+      treeNode: true,
+    },
+    {
+      field: 'imageUrl',
+      title: '分类图片',
+      width: 100,
+      cellRender: { name: 'CellImage' },
+    },
+    {
+      field: 'status',
+      title: '状态',
+      width: 82,
+      cellRender: { name: 'CellTag' },
+    },
+    {
+      align: 'right',
+      field: 'sort',
+      title: '排序',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'name',
+          nameTitle: '名称',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['category:edit'],
+          },
+          {
+            code: 'delete',
+            auth: ['category:delete'],
+          },
+          {
+            code: 'setStatus',
+            label: (row: CategoryApi.RecordItem) => {
+              return row.status === 1 ? '禁用' : '启用';
+            },
+            auth: ['category:setStatus'],
+          },
+        ],
+      },
+      field: 'operation',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      title: '操作',
+      width: 100,
+    },
+  ];
+}
+
+export const useSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'ApiTreeSelect',
+      componentProps: {
+        placeholder: '请输入上级',
+        api: CategoryApi.getTree,
+        showSearch: true,
+        treeNodeFilterProp: 'label',
+      },
+      fieldName: 'parentId',
+      label: '上级',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'name',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'BcCropper',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'imageUrl',
+      label: '分类图片',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+  ];
+};

+ 115 - 0
apps/web-baicai/src/views/shop/goods/category/index.vue

@@ -0,0 +1,115 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { CategoryApi } from '#/api';
+
+import FormEdit from './components/edit.vue';
+import { useColumns, useSearchSchema } from './data.config';
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handelSuccess = () => {
+  reload();
+};
+const handleDelete = async (id: number) => {
+  await CategoryApi.deleteDetail(id);
+  message.success('数据删除成功');
+  handelSuccess();
+};
+
+const handleUpdateStatus = async (record: any) => {
+  await CategoryApi.updateStatus({
+    id: record.id,
+    status: record.status === 1 ? 2 : 1,
+  });
+  handelSuccess();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi
+    .setData({
+      isUpdate,
+      baseData: { id: record.id },
+    })
+    .open();
+};
+
+const handleActionClick = async ({
+  code,
+  row,
+}: OnActionClickParams<CategoryApi.RecordItem>) => {
+  switch (code) {
+    case 'delete': {
+      await handleDelete(row.id);
+      break;
+    }
+    case 'edit': {
+      handleEdit(row, true);
+      break;
+    }
+    case 'setStatus': {
+      await handleUpdateStatus(row);
+      break;
+    }
+  }
+};
+
+const [Grid, { reload }] = useVbenVxeGrid(
+  useTableGridOptions({
+    formOptions: {
+      schema: useSearchSchema(),
+    },
+    gridOptions: {
+      pagerConfig: {
+        enabled: false,
+      },
+      treeConfig: {
+        rowField: 'id',
+        childrenField: 'children',
+        transform: false,
+        expandAll: true,
+      },
+      columns: useColumns(handleActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async (_, formValues) => {
+            return await CategoryApi.getList({
+              ...formValues,
+            });
+          },
+        },
+      },
+    } as VxeTableGridOptions,
+  }),
+);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal @success="handelSuccess" />
+    <Grid>
+      <template #table-title>
+        <span class="border-l-primary border-l-8 border-solid pl-2">
+          分类列表
+        </span>
+      </template>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'category:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增分类
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 73 - 0
apps/web-baicai/src/views/shop/goods/params/components/edit.vue

@@ -0,0 +1,73 @@
+<script lang="ts" setup>
+import { computed, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { ParamsApi } from '#/api';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'UserGradeEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm(
+  useFormOptions({
+    schema: useSchema(),
+  }),
+);
+
+const [Modal, { close, setState, getData, lock, unlock }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  closeOnClickModal: false,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (!valid) return;
+      const values = await getValues();
+      lock();
+      const postParams = unref(modelRef);
+      Object.assign(postParams, values);
+      await (unref(isUpdate)
+        ? ParamsApi.editDetail(postParams as ParamsApi.RecordItem)
+        : ParamsApi.addDetail(postParams as ParamsApi.BasicRecordItem));
+      alert('操作成功');
+
+      emit('success');
+      close();
+    } finally {
+      unlock();
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+
+      if (unref(isUpdate)) {
+        const entity = await ParamsApi.getDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑参数' : '新增参数'));
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form />
+  </Modal>
+</template>

+ 121 - 0
apps/web-baicai/src/views/shop/goods/params/data.config.ts

@@ -0,0 +1,121 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+import type { ParamsApi } from '#/api/shop';
+
+import { z } from '#/adapter';
+import { EnumApi } from '#/api';
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+    {
+      component: 'ApiSelect',
+      fieldName: 'paramsType',
+      label: '类型',
+      componentProps: {
+        api: EnumApi.getList,
+        params: { name: EnumApi.EnumType.SiteMenuType },
+        showSearch: true,
+      },
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<ParamsApi.RecordItem>,
+): VxeTableGridOptions<ParamsApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'name',
+      title: '名称',
+    },
+    {
+      field: 'paramsType',
+      title: '类型',
+      width: 100,
+    },
+    {
+      align: 'left',
+      field: 'value',
+      title: '参数值',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'name',
+          nameTitle: '名称',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['params:edit'],
+          },
+          {
+            code: 'delete',
+            auth: ['params:delete'],
+          },
+        ],
+      },
+      field: 'operation',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      title: '操作',
+      width: 100,
+    },
+  ];
+}
+
+export const useSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'name',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'ApiSelect',
+      componentProps: {
+        api: EnumApi.getList,
+        params: { name: EnumApi.EnumType.SiteMenuType },
+        showSearch: true,
+      },
+      fieldName: 'paramsType',
+      label: '类型',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'value',
+      label: '参数值',
+      dependencies: {
+        triggerFields: ['paramsType'],
+        rules(values) {
+          return [1, 2].includes(values.paramsType)
+            ? z.string()
+            : z.string().optional();
+        },
+      },
+    },
+  ];
+};

+ 96 - 0
apps/web-baicai/src/views/shop/goods/params/index.vue

@@ -0,0 +1,96 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { ParamsApi } from '#/api';
+
+import FormEdit from './components/edit.vue';
+import { useColumns, useSearchSchema } from './data.config';
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handelSuccess = () => {
+  reload();
+};
+const handleDelete = async (id: number) => {
+  await ParamsApi.deleteDetail(id);
+  message.success('数据删除成功');
+  handelSuccess();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi
+    .setData({
+      isUpdate,
+      baseData: { id: record.id },
+    })
+    .open();
+};
+
+const handleActionClick = async ({
+  code,
+  row,
+}: OnActionClickParams<ParamsApi.RecordItem>) => {
+  switch (code) {
+    case 'delete': {
+      await handleDelete(row.id);
+      break;
+    }
+    case 'edit': {
+      handleEdit(row, true);
+      break;
+    }
+  }
+};
+
+const [Grid, { reload }] = useVbenVxeGrid(
+  useTableGridOptions({
+    formOptions: {
+      schema: useSearchSchema(),
+    },
+    gridOptions: {
+      columns: useColumns(handleActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async ({ page }, formValues) => {
+            return await ParamsApi.getPage({
+              pageIndex: page.currentPage,
+              pageSize: page.pageSize,
+              ...formValues,
+            });
+          },
+        },
+      },
+    } as VxeTableGridOptions,
+  }),
+);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal @success="handelSuccess" />
+    <Grid>
+      <template #table-title>
+        <span class="border-l-primary border-l-8 border-solid pl-2">
+          参数列表
+        </span>
+      </template>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'params:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增参数
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 73 - 0
apps/web-baicai/src/views/shop/user/grade/components/edit.vue

@@ -0,0 +1,73 @@
+<script lang="ts" setup>
+import { computed, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { UserGradeApi } from '#/api';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'UserGradeEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues }] = useVbenForm(
+  useFormOptions({
+    schema: useSchema(),
+  }),
+);
+
+const [Modal, { close, setState, getData, lock, unlock }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  closeOnClickModal: false,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (!valid) return;
+      const values = await getValues();
+      lock();
+      const postParams = unref(modelRef);
+      Object.assign(postParams, values);
+      await (unref(isUpdate)
+        ? UserGradeApi.editDetail(postParams as UserGradeApi.RecordItem)
+        : UserGradeApi.addDetail(postParams as UserGradeApi.BasicRecordItem));
+      alert('操作成功');
+
+      emit('success');
+      close();
+    } finally {
+      unlock();
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+
+      if (unref(isUpdate)) {
+        const entity = await UserGradeApi.getDetail(data.baseData.id);
+        modelRef.value = { ...entity };
+        setValues(entity);
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑等级' : '新增等级'));
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form />
+  </Modal>
+</template>

+ 154 - 0
apps/web-baicai/src/views/shop/user/grade/data.config.ts

@@ -0,0 +1,154 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+import type { UserGradeApi } from '#/api/shop';
+
+import { boolOptions } from '#/api/model';
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'title',
+      label: '名称',
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<UserGradeApi.RecordItem>,
+): VxeTableGridOptions<UserGradeApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'title',
+      title: '名称',
+    },
+    {
+      field: 'isDefault',
+      title: '默认',
+      width: 82,
+      cellRender: { name: 'CellTag', options: boolOptions },
+    },
+    {
+      field: 'isUpgrade',
+      title: '可升级',
+      width: 82,
+      cellRender: { name: 'CellTag', options: boolOptions },
+    },
+    {
+      align: 'right',
+      field: 'exp',
+      title: '升级所需值',
+      width: 100,
+    },
+    {
+      field: 'status',
+      title: '状态',
+      width: 82,
+      cellRender: { name: 'CellTag' },
+    },
+    {
+      align: 'right',
+      field: 'sort',
+      title: '排序',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'title',
+          nameTitle: '名称',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['user-grade:edit'],
+          },
+          {
+            code: 'delete',
+            auth: ['user-grade:delete'],
+          },
+          {
+            code: 'setStatus',
+            label: (row: UserGradeApi.RecordItem) => {
+              return row.status === 1 ? '禁用' : '启用';
+            },
+            auth: ['user-grade:setStatus'],
+          },
+        ],
+      },
+      field: 'operation',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      title: '操作',
+      width: 60,
+    },
+  ];
+}
+
+export const useSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'title',
+      label: '名称',
+      rules: 'required',
+    },
+    {
+      component: 'Switch',
+      componentProps: {
+        placeholder: '请输入名称',
+        class: 'w-auto',
+      },
+      defaultValue: false,
+      fieldName: 'isDefault',
+      label: '默认',
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+    {
+      component: 'Switch',
+      componentProps: {
+        placeholder: '请输入',
+        class: 'w-auto',
+      },
+      defaultValue: false,
+      fieldName: 'isUpgrade',
+      label: '可升级',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      dependencies: {
+        triggerFields: ['isUpgrade'],
+        show(values) {
+          return values.isUpgrade;
+        },
+      },
+      fieldName: 'exp',
+      label: '升级值',
+      rules: 'required',
+    },
+  ];
+};

+ 108 - 0
apps/web-baicai/src/views/shop/user/grade/index.vue

@@ -0,0 +1,108 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { UserGradeApi } from '#/api';
+
+import FormEdit from './components/edit.vue';
+import { useColumns, useSearchSchema } from './data.config';
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handelSuccess = () => {
+  reload();
+};
+const handleDelete = async (id: number) => {
+  await UserGradeApi.deleteDetail(id);
+  message.success('数据删除成功');
+  handelSuccess();
+};
+
+const handleUpdateStatus = async (record: any) => {
+  await UserGradeApi.updateStatus({
+    id: record.id,
+    status: record.status === 1 ? 2 : 1,
+  });
+  handelSuccess();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi
+    .setData({
+      isUpdate,
+      baseData: { id: record.id },
+    })
+    .open();
+};
+
+const handleActionClick = async ({
+  code,
+  row,
+}: OnActionClickParams<UserGradeApi.RecordItem>) => {
+  switch (code) {
+    case 'delete': {
+      await handleDelete(row.id);
+      break;
+    }
+    case 'edit': {
+      handleEdit(row, true);
+      break;
+    }
+    case 'setStatus': {
+      await handleUpdateStatus(row);
+      break;
+    }
+  }
+};
+
+const [Grid, { reload }] = useVbenVxeGrid(
+  useTableGridOptions({
+    formOptions: {
+      schema: useSearchSchema(),
+    },
+    gridOptions: {
+      columns: useColumns(handleActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async ({ page }, formValues) => {
+            return await UserGradeApi.getPage({
+              pageIndex: page.currentPage,
+              pageSize: page.pageSize,
+              ...formValues,
+            });
+          },
+        },
+      },
+    } as VxeTableGridOptions,
+  }),
+);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal @success="handelSuccess" />
+    <Grid>
+      <template #table-title>
+        <span class="border-l-primary border-l-8 border-solid pl-2">
+          等级列表
+        </span>
+      </template>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'user-grade:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增等级
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 88 - 0
apps/web-baicai/src/views/shop/user/info/components/edit.vue

@@ -0,0 +1,88 @@
+<script lang="ts" setup>
+import { computed, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { ShopUserApi } from '#/api';
+import { encrypt } from '#/utils';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'ShopUserEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+
+const [Form, { validate, setValues, getValues, updateSchema }] = useVbenForm(
+  useFormOptions({
+    schema: useSchema(),
+    wrapperClass: 'grid-cols-2',
+  }),
+);
+
+const [Modal, { close, setState, getData, lock, unlock }] = useVbenModal({
+  fullscreenButton: false,
+  draggable: true,
+  closeOnClickModal: false,
+  onCancel() {
+    close();
+  },
+  onConfirm: async () => {
+    try {
+      const { valid } = await validate();
+      if (!valid) return;
+      const values = await getValues();
+      lock();
+      const postParams = unref(modelRef);
+      const data: any = {};
+      if (values.password) {
+        data.password = encrypt(values.password);
+      }
+      Object.assign(postParams, values, { ...data });
+      await (unref(isUpdate)
+        ? ShopUserApi.editDetail(postParams as ShopUserApi.RecordItem)
+        : ShopUserApi.addDetail(postParams as ShopUserApi.BasicRecordItem));
+      alert('操作成功');
+
+      emit('success');
+      close();
+    } finally {
+      unlock();
+    }
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (isOpen) {
+      setState({ loading: true });
+      const data = getData<Record<string, any>>();
+      isUpdate.value = !!data.isUpdate;
+      modelRef.value = { ...data.baseData };
+
+      if (unref(isUpdate)) {
+        const entity = await ShopUserApi.getDetail(data.baseData.id);
+        const newEntity = { ...entity.user, ...entity };
+        delete newEntity.user;
+        modelRef.value = { ...newEntity };
+        setValues(newEntity);
+        updateSchema([
+          { fieldName: 'password', componentProps: { disabled: true } },
+        ]);
+      } else {
+        updateSchema([
+          { fieldName: 'password', componentProps: { disabled: false } },
+        ]);
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑用户' : '新增用户'));
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form />
+  </Modal>
+</template>

+ 217 - 0
apps/web-baicai/src/views/shop/user/info/data.config.ts

@@ -0,0 +1,217 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+
+import { z } from '#/adapter';
+import { EnumApi } from '#/api';
+import { ShopUserApi, UserGradeApi } from '#/api/shop';
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'account',
+      label: '账号',
+    },
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '姓名',
+    },
+    {
+      component: 'ApiSelect',
+      componentProps: {
+        api: UserGradeApi.getOptions,
+        showSearch: true,
+        optionFilterProp: 'label',
+      },
+      fieldName: 'grade',
+      label: '会员等级',
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<ShopUserApi.RecordItem>,
+): VxeTableGridOptions<ShopUserApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'user.account',
+      title: '账号',
+      width: 200,
+    },
+    { align: 'left', field: 'user.realName', title: '姓名', width: 120 },
+    { align: 'left', field: 'gradeName', title: '用户等级', width: 120 },
+    { align: 'right', field: 'point', title: '积分', width: 100 },
+    { align: 'right', field: 'balance', title: '余额', width: 100 },
+    {
+      field: 'user.status',
+      title: '状态',
+      width: 82,
+      cellRender: { name: 'CellTag' },
+    },
+    { align: 'left', field: 'user.remark', title: '备注' },
+    {
+      align: 'right',
+      field: 'user.sort',
+      title: '排序',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'user.account',
+          nameTitle: '账号',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['shop-user:edit'],
+          },
+          {
+            code: 'resetPwd',
+            label: '重置密码',
+            auth: ['shop-user:resetPwd'],
+            confirm: {
+              title: '重置密码提示',
+              content: `确定要重置选择的记录吗?`,
+            },
+          },
+          {
+            code: 'delete',
+            auth: ['shop-user:delete'],
+          },
+          {
+            code: 'setStatus',
+            label: (row: ShopUserApi.RecordItem) => {
+              return row.user.status === 1 ? '禁用' : '启用';
+            },
+            auth: ['shop-user:setStatus'],
+          },
+        ],
+      },
+      field: 'operation',
+      fixed: 'right',
+      headerAlign: 'center',
+      showOverflow: false,
+      title: '操作',
+      width: 160,
+    },
+  ];
+}
+
+export const useSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'ApiSelect',
+      componentProps: {
+        api: UserGradeApi.getOptions,
+        showSearch: true,
+        optionFilterProp: 'label',
+        autoSelect: 'first',
+      },
+      fieldName: 'grade',
+      label: '会员等级',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入账号',
+      },
+      fieldName: 'account',
+      label: '账号',
+      rules: z
+        .string()
+        // .min(3, '账号至少需要3个字符')
+        .regex(
+          /^[a-z]\w{2,}$/i,
+          '账号只能由字母、数字、下划线组成并以字母开头',
+        ),
+    },
+    {
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: '请输入密码',
+      },
+      fieldName: 'password',
+      label: '密码',
+      // rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入姓名',
+      },
+      fieldName: 'realName',
+      label: '姓名',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入昵称',
+      },
+      fieldName: 'nickName',
+      label: '昵称',
+    },
+    {
+      component: 'ApiRadio',
+      componentProps: {
+        api: EnumApi.getList,
+        params: { name: EnumApi.EnumType.Gender },
+        optionType: 'button',
+        buttonStyle: 'solid',
+        autoSelect: 'last',
+      },
+      fieldName: 'sex',
+      label: '性别',
+    },
+    {
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '请输入',
+        valueFormat: 'YYYY-MM-DD',
+      },
+      fieldName: 'birthday',
+      label: '生日',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入电话',
+      },
+      fieldName: 'phone',
+      label: '电话',
+      rules: z
+        .string()
+        .regex(/^1[3-9]\d{9}$/, '电话格式错误')
+        .optional(),
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入邮箱',
+      },
+      fieldName: 'email',
+      label: '邮箱',
+      rules: z.string().email('邮箱格式错误').optional(),
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'remark',
+      label: '备注',
+      formItemClass: 'col-span-2 items-baseline',
+    },
+  ];
+};

+ 132 - 0
apps/web-baicai/src/views/shop/user/info/index.vue

@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { confirm, Page, useVbenModal } from '@vben/common-ui';
+
+import { useClipboard } from '@vueuse/core';
+import { Button, message } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { ShopUserApi, UserApi } from '#/api';
+
+import FormEdit from './components/edit.vue';
+import { useColumns, useSearchSchema } from './data.config';
+
+const { copy } = useClipboard({ legacy: true });
+
+const [FormEditModal, formEditApi] = useVbenModal({
+  connectedComponent: FormEdit,
+});
+
+const handelSuccess = () => {
+  reload();
+};
+
+const handelResetPassword = async (record: any) => {
+  const data = await UserApi.resetPassword([record.user.id]);
+  confirm({
+    title: '重置成功',
+    content: `新密码为:${data}`,
+    confirmText: '复制密码',
+    beforeClose: ({ isConfirm }: { isConfirm: boolean }) => {
+      if (isConfirm) {
+        copy(data);
+        message.success('密码复制成功!');
+      }
+      return true;
+    },
+  });
+};
+
+const handleDelete = async (id: number) => {
+  await ShopUserApi.deleteDetail(id);
+  message.success('数据删除成功');
+  handelSuccess();
+};
+
+const handleUpdateStatus = async (record: any) => {
+  await UserApi.updateStatus({
+    id: record.user.id,
+    status: record.user.status === 1 ? 2 : 1,
+  });
+  handelSuccess();
+};
+
+const handleEdit = (record: any, isUpdate: boolean) => {
+  formEditApi
+    .setData({
+      isUpdate,
+      baseData: { id: record.id },
+    })
+    .open();
+};
+
+const handleActionClick = async ({
+  code,
+  row,
+}: OnActionClickParams<ShopUserApi.RecordItem>) => {
+  switch (code) {
+    case 'delete': {
+      await handleDelete(row.id);
+      break;
+    }
+    case 'edit': {
+      handleEdit(row, true);
+      break;
+    }
+    case 'resetPwd': {
+      handelResetPassword(row);
+      break;
+    }
+    case 'setStatus': {
+      await handleUpdateStatus(row);
+      break;
+    }
+  }
+};
+
+const [Grid, { reload }] = useVbenVxeGrid(
+  useTableGridOptions({
+    formOptions: {
+      schema: useSearchSchema(),
+    },
+    gridOptions: {
+      columns: useColumns(handleActionClick),
+      proxyConfig: {
+        ajax: {
+          query: async ({ page }, formValues) => {
+            return await ShopUserApi.getPage({
+              pageIndex: page.currentPage,
+              pageSize: page.pageSize,
+              ...formValues,
+            });
+          },
+        },
+      },
+    } as VxeTableGridOptions,
+  }),
+);
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormEditModal @success="handelSuccess" />
+    <Grid>
+      <template #table-title>
+        <span class="border-l-primary border-l-8 border-solid pl-2">
+          用户列表
+        </span>
+      </template>
+      <template #toolbar-tools>
+        <Button
+          class="mr-2"
+          type="primary"
+          v-access:code="'shop-user:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增用户
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>

+ 9 - 0
apps/web-baicai/src/views/system/design/generate/info/data.config.ts

@@ -164,6 +164,15 @@ export const useSchema = (): VbenFormSchema[] => {
       label: '业务名',
       rules: 'required',
     },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'description',
+      label: '说明',
+      rules: 'required',
+    },
     {
       component: 'Input',
       componentProps: {

+ 9 - 0
apps/web-baicai/src/views/system/design/generate/info/index.vue

@@ -46,11 +46,20 @@ const handlePreview = (record: any) => {
     .open();
 };
 
+const handleCode = async (id: number) => {
+  await GenerateApi.generate(id);
+  message.success('代码生成成功');
+};
+
 const handleActionClick = async ({
   code,
   row,
 }: OnActionClickParams<GenerateApi.RecordItem>) => {
   switch (code) {
+    case 'code': {
+      await handleCode(row.id);
+      break;
+    }
     case 'delete': {
       await handleDelete(row.id);
       break;

+ 2 - 1
apps/web-baicai/src/views/system/user/data.config.ts

@@ -116,7 +116,7 @@ export const useSchema = (): VbenFormSchema[] => {
       },
       fieldName: 'password',
       label: '密码',
-      rules: 'required',
+      // rules: 'required',
     },
     {
       component: 'Input',
@@ -151,6 +151,7 @@ export const useSchema = (): VbenFormSchema[] => {
       component: 'DatePicker',
       componentProps: {
         placeholder: '请输入',
+        valueFormat: 'YYYY-MM-DD',
       },
       fieldName: 'birthday',
       label: '生日',

Некоторые файлы не были показаны из-за большого количества измененных файлов