ソースを参照

feat: 添加规格

snihwxf 3 ヶ月 前
コミット
69f9a36688

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

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

+ 10 - 1
apps/web-baicai/src/api/shop/goods/params.ts

@@ -1,4 +1,8 @@
-import type { BasicFetchResult, BasicPageParams } from '#/api/model';
+import type {
+  BasicFetchResult,
+  BasicOptionResult,
+  BasicPageParams,
+} from '#/api/model';
 
 import { requestClient } from '#/api/request';
 
@@ -21,6 +25,11 @@ export namespace ParamsApi {
   export const getPage = (params: PageParams) =>
     requestClient.get<PageResult>('/shop/params/page', { params });
 
+  export const getOptions = (id: number) =>
+    requestClient.get<BasicOptionResult[]>('/shop/params/options', {
+      params: { id },
+    });
+
   export const getDetail = (id: number) =>
     requestClient.get<RecordItem>('/shop/params/entity', {
       params: { id },

+ 46 - 0
apps/web-baicai/src/api/shop/goods/spec.ts

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

+ 29 - 10
apps/web-baicai/src/views/shop/goods/params/data.config.ts

@@ -3,10 +3,18 @@ import type {
   VbenFormSchema,
   VxeTableGridOptions,
 } from '#/adapter';
+import type { BasicOptionResult } from '#/api/model';
 import type { ParamsApi } from '#/api/shop';
 
+import { h } from 'vue';
+
 import { z } from '#/adapter';
-import { EnumApi } from '#/api';
+
+export const paramsTypeOptions: BasicOptionResult[] = [
+  { label: '文本框', value: 0, color: 'success' },
+  { label: '复选框', value: 1, color: 'error' },
+  { label: '单选框', value: 2, color: 'warning' },
+];
 
 export const useSearchSchema = (): VbenFormSchema[] => {
   return [
@@ -16,13 +24,11 @@ export const useSearchSchema = (): VbenFormSchema[] => {
       label: '名称',
     },
     {
-      component: 'ApiSelect',
+      component: 'Select',
       fieldName: 'paramsType',
       label: '类型',
       componentProps: {
-        api: EnumApi.getList,
-        params: { name: EnumApi.EnumType.SiteMenuType },
-        showSearch: true,
+        options: paramsTypeOptions,
       },
     },
   ];
@@ -37,17 +43,19 @@ export function useColumns(
       align: 'left',
       field: 'name',
       title: '名称',
+      width: 82,
     },
     {
       field: 'paramsType',
       title: '类型',
       width: 100,
+      cellRender: { name: 'CellTag', options: paramsTypeOptions },
     },
     {
       align: 'left',
       field: 'value',
       title: '参数值',
-      width: 82,
+      slots: { default: 'value' },
     },
     {
       align: 'right',
@@ -91,12 +99,11 @@ export const useSchema = (): VbenFormSchema[] => {
       rules: 'required',
     },
     {
-      component: 'ApiSelect',
+      component: 'Select',
       componentProps: {
-        api: EnumApi.getList,
-        params: { name: EnumApi.EnumType.SiteMenuType },
-        showSearch: true,
+        options: paramsTypeOptions,
       },
+      defaultValue: 0,
       fieldName: 'paramsType',
       label: '类型',
       rules: 'required',
@@ -117,5 +124,17 @@ export const useSchema = (): VbenFormSchema[] => {
         },
       },
     },
+    {
+      component: h('div', { class: 'text-sm font-thin' }, [
+        h(
+          'div',
+          null,
+          '如果选择的是【单选】或者【复选框】,则多个参数值之间使用小写逗号(,)分隔。',
+        ),
+      ]),
+      formItemClass: 'items-start',
+      fieldName: 'remark',
+      label: '说明',
+    },
   ];
 };

+ 6 - 1
apps/web-baicai/src/views/shop/goods/params/index.vue

@@ -3,7 +3,7 @@ import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
 
 import { Page, useVbenModal } from '@vben/common-ui';
 
-import { Button, message } from 'ant-design-vue';
+import { Button, message, Tag } from 'ant-design-vue';
 
 import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
 import { ParamsApi } from '#/api';
@@ -91,6 +91,11 @@ const [Grid, { reload }] = useVbenVxeGrid(
           新增参数
         </Button>
       </template>
+      <template #value="{ row }">
+        <Tag color="blue" v-for="item in row.value?.split(',')" :key="item">
+          {{ item }}
+        </Tag>
+      </template>
     </Grid>
   </Page>
 </template>

+ 115 - 0
apps/web-baicai/src/views/shop/goods/spec/components/edit.vue

@@ -0,0 +1,115 @@
+<script lang="ts" setup>
+import { computed, reactive, ref, unref } from 'vue';
+
+import { alert, useVbenModal } from '@vben/common-ui';
+
+import { Button, Input } from 'ant-design-vue';
+
+import { useFormOptions, useVbenForm } from '#/adapter';
+import { SpecApi } from '#/api';
+import { Icon } from '#/components/icon';
+
+import { useSchema } from '../data.config';
+
+defineOptions({
+  name: 'SpecEdit',
+});
+const emit = defineEmits(['success']);
+const modelRef = ref<Record<string, any>>({});
+const isUpdate = ref(true);
+const state = reactive<{ specDetails: any[] }>({
+  specDetails: [],
+});
+
+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);
+      postParams.specDetails = state.specDetails;
+      await (unref(isUpdate)
+        ? SpecApi.editDetail(postParams as SpecApi.RecordItem)
+        : SpecApi.addDetail(postParams as SpecApi.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 SpecApi.getDetail(data.baseData.id);
+        state.specDetails = entity.specDetails;
+        modelRef.value = { ...entity };
+        setValues(entity);
+      } else {
+        handleAdd();
+      }
+      setState({ loading: false });
+    }
+  },
+});
+
+const getTitle = computed(() => (unref(isUpdate) ? '编辑规格' : '新增规格'));
+
+const handleAdd = () => {
+  state.specDetails.push({ name: '' });
+};
+
+const handleDelete = (index: number) => {
+  state.specDetails.splice(index, 1);
+};
+</script>
+<template>
+  <Modal class="w-[1000px]" :title="getTitle">
+    <Form>
+      <template #specDetails>
+        <div class="w-full">
+          <div
+            v-for="(item, index) in state.specDetails"
+            :key="index"
+            class="mb-4 flex w-full items-center last:mb-0"
+          >
+            <Input.Group compact class="!flex">
+              <Input v-model:value="item.name" />
+              <Button type="primary" v-if="index === 0" @click="handleAdd">
+                <template #icon>
+                  <Icon icon="material-symbols:add-rounded" />
+                </template>
+              </Button>
+              <Button type="primary" danger v-else @click="handleDelete(index)">
+                <template #icon>
+                  <Icon icon="material-symbols:delete-outline" />
+                </template>
+              </Button>
+            </Input.Group>
+          </div>
+        </div>
+      </template>
+    </Form>
+  </Modal>
+</template>

+ 120 - 0
apps/web-baicai/src/views/shop/goods/spec/data.config.ts

@@ -0,0 +1,120 @@
+import type {
+  OnActionClickFn,
+  VbenFormSchema,
+  VxeTableGridOptions,
+} from '#/adapter';
+import type { BasicOptionResult } from '#/api/model';
+import type { SpecApi } from '#/api/shop';
+
+import { h } from 'vue';
+
+export const paramsTypeOptions: BasicOptionResult[] = [
+  { label: '文本框', value: 0, color: 'success' },
+  { label: '复选框', value: 1, color: 'error' },
+  { label: '单选框', value: 2, color: 'warning' },
+];
+
+export const useSearchSchema = (): VbenFormSchema[] => {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '名称',
+    },
+  ];
+};
+
+export function useColumns(
+  onActionClick?: OnActionClickFn<SpecApi.RecordItem>,
+): VxeTableGridOptions<SpecApi.RecordItem>['columns'] {
+  return [
+    { title: '序号', type: 'seq', width: 50 },
+    {
+      align: 'left',
+      field: 'name',
+      title: '名称',
+      width: 82,
+    },
+    {
+      align: 'left',
+      field: 'specDetails',
+      title: '规格值',
+      slots: { default: 'specDetails' },
+    },
+    {
+      field: 'sort',
+      title: '排序',
+      width: 82,
+    },
+    {
+      align: 'right',
+      cellRender: {
+        attrs: {
+          nameField: 'name',
+          nameTitle: '名称',
+          onClick: onActionClick,
+        },
+        name: 'CellAction',
+        options: [
+          {
+            code: 'edit',
+            auth: ['spec:edit'],
+          },
+          {
+            code: 'delete',
+            auth: ['spec: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: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入排序',
+      },
+      fieldName: 'sort',
+      label: '排序',
+      rules: 'required',
+    },
+    {
+      component: h('div', { class: 'text-sm font-thin' }, [
+        h(
+          'div',
+          null,
+          'SKU模型值只支持:中文、英文、数字、中文输入大写符号、英文小写二个符号(-、/)',
+        ),
+      ]),
+      formItemClass: 'items-start',
+      fieldName: 'remark',
+      label: '说明',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'specDetails',
+      label: '规格值',
+    },
+  ];
+};

+ 101 - 0
apps/web-baicai/src/views/shop/goods/spec/index.vue

@@ -0,0 +1,101 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { Button, message, Tag } from 'ant-design-vue';
+
+import { useTableGridOptions, useVbenVxeGrid } from '#/adapter';
+import { SpecApi } 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 SpecApi.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<SpecApi.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 SpecApi.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="'spec:add'"
+          @click="() => handleEdit({}, false)"
+        >
+          新增规格
+        </Button>
+      </template>
+      <template #specDetails="{ row }">
+        <Tag color="blue" v-for="item in row.specDetails" :key="item.id">
+          {{ item.name }}
+        </Tag>
+      </template>
+    </Grid>
+  </Page>
+</template>