Forráskód Böngészése

```
feat: 新增大健康活动管理功能并优化服务配置

- 新增大健康活动管理模块,包括活动列表展示、新增、编辑、删除功能
- 添加政府端认领记录页面和相关API接口
- 完善健康商品编辑页面功能,增加富文本编辑器支持
- 配置新的测试环境服务地址,更新API请求接口
- 添加国际化语言配置,支持新增页面路由和组件
- 增加日期选择器等新组件支持,完善表单验证规则
```

wenjie 14 órája
szülő
commit
c592475b7c

+ 2 - 2
.env.test

@@ -7,9 +7,9 @@
 # VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun #付
 # VITE_SERVICE_BASE_URL=https://smqjh.api.zswlgz.com
 # VITE_SERVICE_BASE_URL=https://735a1bda.r24.cpolar.top #黄
-VITE_SERVICE_BASE_URL=https://55b1a442.r39.cpolar.top
+# VITE_SERVICE_BASE_URL=https://1fa1ad42.r9.vip.cpolar.cn
 
-# VITE_SERVICE_BASE_URL=http://47.109.84.152:8081 #打包测试本地服务器
+VITE_SERVICE_BASE_URL=http://47.109.84.152:8081 #打包测试本地服务器
 # VITE_SERVICE_BASE_URL=/plt #测试打包服务器
 
 # other backend service base url, test environment

+ 5 - 1
src/locales/langs/en-us.ts

@@ -278,7 +278,11 @@ const local: App.I18n.Schema = {
     'film-manage_setprice': '',
     'goods-center_virtual-goods': '',
     'goods-center_health-goods': '',
-    'goods-center_edit-health-goods': ''
+    'goods-center_edit-health-goods': '',
+    'djk-manage': '',
+    'djk-manage_activity': '',
+    'government_claim-records': '',
+    'djk-manage_edit-activity': ''
   },
   page: {
     login: {

+ 5 - 1
src/locales/langs/zh-cn.ts

@@ -275,7 +275,11 @@ const local: App.I18n.Schema = {
     'film-manage_setprice': '',
     'goods-center_virtual-goods': '',
     'goods-center_health-goods': '',
-    'goods-center_edit-health-goods': ''
+    'goods-center_edit-health-goods': '',
+    'djk-manage': '',
+    'djk-manage_activity': '',
+    'government_claim-records': '',
+    'djk-manage_edit-activity': ''
   },
   page: {
     login: {

+ 3 - 0
src/router/elegant/imports.ts

@@ -22,6 +22,8 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   login: () => import("@/views/_builtin/login/index.vue"),
   device_manage: () => import("@/views/device/manage/index.vue"),
   "device_terminal-manage": () => import("@/views/device/terminal-manage/index.vue"),
+  "djk-manage_activity": () => import("@/views/djk-manage/activity/index.vue"),
+  "djk-manage_edit-activity": () => import("@/views/djk-manage/edit-activity/index.vue"),
   "film-manage_config": () => import("@/views/film-manage/config/index.vue"),
   "film-manage_film-list": () => import("@/views/film-manage/film-list/index.vue"),
   "film-manage_setprice": () => import("@/views/film-manage/setprice/index.vue"),
@@ -30,6 +32,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   "goods-center_store-goods": () => import("@/views/goods-center/store-goods/index.vue"),
   "goods-center_type-admin": () => import("@/views/goods-center/type-admin/index.vue"),
   "goods-center_virtual-goods": () => import("@/views/goods-center/virtual-goods/index.vue"),
+  "government_claim-records": () => import("@/views/government/claim-records/index.vue"),
   "government_government-list": () => import("@/views/government/government-list/index.vue"),
   government_points: () => import("@/views/government/points/index.vue"),
   "government_user-list": () => import("@/views/government/user-list/index.vue"),

+ 38 - 0
src/router/elegant/routes.ts

@@ -68,6 +68,35 @@ export const generatedRoutes: GeneratedRoute[] = [
       }
     ]
   },
+  {
+    name: 'djk-manage',
+    path: '/djk-manage',
+    component: 'layout.base',
+    meta: {
+      title: 'djk-manage',
+      i18nKey: 'route.djk-manage'
+    },
+    children: [
+      {
+        name: 'djk-manage_activity',
+        path: '/djk-manage/activity',
+        component: 'view.djk-manage_activity',
+        meta: {
+          title: 'djk-manage_activity',
+          i18nKey: 'route.djk-manage_activity'
+        }
+      },
+      {
+        name: 'djk-manage_edit-activity',
+        path: '/djk-manage/edit-activity',
+        component: 'view.djk-manage_edit-activity',
+        meta: {
+          title: 'djk-manage_edit-activity',
+          i18nKey: 'route.djk-manage_edit-activity'
+        }
+      }
+    ]
+  },
   {
     name: 'film-manage',
     path: '/film-manage',
@@ -171,6 +200,15 @@ export const generatedRoutes: GeneratedRoute[] = [
       i18nKey: 'route.government'
     },
     children: [
+      {
+        name: 'government_claim-records',
+        path: '/government/claim-records',
+        component: 'view.government_claim-records',
+        meta: {
+          title: 'government_claim-records',
+          i18nKey: 'route.government_claim-records'
+        }
+      },
       {
         name: 'government_government-list',
         path: '/government/government-list',

+ 4 - 0
src/router/elegant/transform.ts

@@ -184,6 +184,9 @@ const routeMap: RouteMap = {
   "device": "/device",
   "device_manage": "/device/manage",
   "device_terminal-manage": "/device/terminal-manage",
+  "djk-manage": "/djk-manage",
+  "djk-manage_activity": "/djk-manage/activity",
+  "djk-manage_edit-activity": "/djk-manage/edit-activity",
   "film-manage": "/film-manage",
   "film-manage_config": "/film-manage/config",
   "film-manage_film-list": "/film-manage/film-list",
@@ -195,6 +198,7 @@ const routeMap: RouteMap = {
   "goods-center_type-admin": "/goods-center/type-admin",
   "goods-center_virtual-goods": "/goods-center/virtual-goods",
   "government": "/government",
+  "government_claim-records": "/government/claim-records",
   "government_government-list": "/government/government-list",
   "government_points": "/government/points",
   "government_user-list": "/government/user-list",

+ 60 - 0
src/service/api/djk-manage/activity/index.ts

@@ -0,0 +1,60 @@
+import { request } from '@/service/request';
+/**
+ * 分页获取活动
+ * @param params
+ * @returns
+ */
+export function fetchActivityList(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/djk/welfare/page',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ *
+ * 删除活动
+ * @returns
+ */
+export function fetchDelActivity(id: string) {
+  return request({
+    url: `/smqjh-pms/api/v1/djk/welfare/delete/${id}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 保存活动
+ * @returns
+ */
+export function fetchAddActivity(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/djk/welfare/add',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 保存活动
+ * @returns
+ */
+export function fetchEditActivity(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/djk/welfare/update',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 活动详情
+ * @returns
+ */
+export function fetchActivityDetail(id: any) {
+  return request({
+    url: `/smqjh-pms/api/v1/djk/welfare/info/${id}`,
+    method: 'get'
+  });
+}

+ 6 - 6
src/service/api/goods-center/health-goods/index.ts

@@ -50,7 +50,7 @@ export function fetchDelGoods(id: string) {
  * @returns
  */
 export function fetchAddGoods(data: any) {
-  return request<Api.goods.Channel[]>({
+  return request({
     url: '/smqjh-pms/api/v1/goods/addDJKGoods',
     method: 'post',
     data
@@ -62,7 +62,7 @@ export function fetchAddGoods(data: any) {
  * @returns
  */
 export function fetchEditGoods(data: any) {
-  return request<Api.goods.Channel[]>({
+  return request({
     url: '/smqjh-pms/api/v1/goods/updateDJKGoods',
     method: 'put',
     data
@@ -73,10 +73,10 @@ export function fetchEditGoods(data: any) {
  * 商品详情
  * @returns
  */
-export function fetchGoodsDetail(data: any) {
-  return request<Api.goods.Channel[]>({
+export function fetchGoodsDetail(id: any) {
+  return request({
     url: '/smqjh-pms/api/v1/goods/info',
-    method: 'put',
-    data
+    method: 'get',
+    params: { id }
   });
 }

+ 35 - 0
src/service/api/government/claim-records/index.ts

@@ -0,0 +1,35 @@
+import { request } from '@/service/request';
+/**
+ * 分页获取商品
+ * @param params
+ * @returns
+ */
+export function fetchList(data: any) {
+  return request({
+    url: '/smqjh-system/app-api/v1/claim/page',
+    method: 'get',
+    params: data
+  });
+}
+
+/**
+ * 获取全部渠道商
+ * @returns
+ */
+export function fetchGetAllChannelList() {
+  return request<Api.goods.Channel[]>({
+    url: '/smqjh-system/api/v1/channel/listAll',
+    method: 'get'
+  });
+}
+
+/**
+ * 获取一级渠道商
+ * @returns
+ */
+export function fetchGetChannelList() {
+  return request<Api.goods.Channel[]>({
+    url: '/smqjh-system/api/v1/channel/listFirstChannel',
+    method: 'get'
+  });
+}

+ 11 - 0
src/service/api/order-manage/normal-order/index.ts

@@ -98,3 +98,14 @@ export function fetchDeliveryOrder(data: any) {
     data
   });
 }
+
+/**
+ * 核销
+ */
+export function fetchAudit(data: any) {
+  return request({
+    url: '/smqjh-oms/api/v1/djkOrder/backendVerification',
+    method: 'get',
+    params: data
+  });
+}

+ 63 - 0
src/service/api/xsb-manage/store-info/index.ts

@@ -12,3 +12,66 @@ export function fetchGetStoreList(data: any) {
     params: data
   });
 }
+
+/**
+ * 分页获取区域
+ * @param data
+ * @returns
+ */
+export function fetchGetAreaList(data: any) {
+  return request<Api.Store.ShopDetail[]>({
+    url: '/smqjh-pms/api/v1/goods/area',
+    method: 'GET',
+    params: data
+  });
+}
+
+/**
+ * 添加门店
+ * @param data
+ * @returns
+ */
+export function fetchAddShop(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/shopDetail/djkShopInfoSave',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 编辑门店
+ * @param data
+ * @returns
+ */
+export function fetchEditShop(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/shopDetail/djkShopInfoUpdate',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 删除门店
+ * @param data
+ * @returns
+ */
+export function fetchDelShop(shopId: any) {
+  return request({
+    url: `/smqjh-pms/api/v1/shopDetail/${shopId}`,
+    method: 'delete'
+  });
+}
+/**
+ * 获取门店城市
+ * @param data
+ * @returns
+ */
+export function fetchGetShopCity(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/shopDetail/getShopCity',
+    method: 'GET',
+    params: data
+  });
+}

+ 3 - 0
src/typings/components.d.ts

@@ -34,6 +34,7 @@ declare module 'vue' {
     'IconIconParkTwotone:mailDownload': typeof import('~icons/icon-park-twotone/mail-download')['default']
     IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
     IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
+    'IconMaterialSymbols:addCircle': typeof import('~icons/material-symbols/add-circle')['default']
     IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
     IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
     IconMdiDrag: typeof import('~icons/mdi/drag')['default']
@@ -57,6 +58,7 @@ declare module 'vue' {
     NCollapseItem: typeof import('naive-ui')['NCollapseItem']
     NColorPicker: typeof import('naive-ui')['NColorPicker']
     NDataTable: typeof import('naive-ui')['NDataTable']
+    NDatePicker: typeof import('naive-ui')['NDatePicker']
     NDialogProvider: typeof import('naive-ui')['NDialogProvider']
     NDivider: typeof import('naive-ui')['NDivider']
     NDrawer: typeof import('naive-ui')['NDrawer']
@@ -65,6 +67,7 @@ declare module 'vue' {
     NEmpty: typeof import('naive-ui')['NEmpty']
     NForm: typeof import('naive-ui')['NForm']
     NFormItem: typeof import('naive-ui')['NFormItem']
+    NFormItemGi: typeof import('naive-ui')['NFormItemGi']
     NGi: typeof import('naive-ui')['NGi']
     NGrid: typeof import('naive-ui')['NGrid']
     NGridItem: typeof import('naive-ui')['NGridItem']

+ 8 - 0
src/typings/elegant-router.d.ts

@@ -38,6 +38,9 @@ declare module "@elegant-router/types" {
     "device": "/device";
     "device_manage": "/device/manage";
     "device_terminal-manage": "/device/terminal-manage";
+    "djk-manage": "/djk-manage";
+    "djk-manage_activity": "/djk-manage/activity";
+    "djk-manage_edit-activity": "/djk-manage/edit-activity";
     "film-manage": "/film-manage";
     "film-manage_config": "/film-manage/config";
     "film-manage_film-list": "/film-manage/film-list";
@@ -49,6 +52,7 @@ declare module "@elegant-router/types" {
     "goods-center_type-admin": "/goods-center/type-admin";
     "goods-center_virtual-goods": "/goods-center/virtual-goods";
     "government": "/government";
+    "government_claim-records": "/government/claim-records";
     "government_government-list": "/government/government-list";
     "government_points": "/government/points";
     "government_user-list": "/government/user-list";
@@ -127,6 +131,7 @@ declare module "@elegant-router/types" {
     | "404"
     | "500"
     | "device"
+    | "djk-manage"
     | "film-manage"
     | "goods-center"
     | "government"
@@ -164,6 +169,8 @@ declare module "@elegant-router/types" {
     | "login"
     | "device_manage"
     | "device_terminal-manage"
+    | "djk-manage_activity"
+    | "djk-manage_edit-activity"
     | "film-manage_config"
     | "film-manage_film-list"
     | "film-manage_setprice"
@@ -172,6 +179,7 @@ declare module "@elegant-router/types" {
     | "goods-center_store-goods"
     | "goods-center_type-admin"
     | "goods-center_virtual-goods"
+    | "government_claim-records"
     | "government_government-list"
     | "government_points"
     | "government_user-list"

+ 226 - 0
src/views/djk-manage/activity/index.vue

@@ -0,0 +1,226 @@
+<script setup lang="tsx">
+import { NButton, NImage, NSwitch } from 'naive-ui';
+// import dayjs from 'dayjs';
+import { router } from '@/router';
+import {
+  // fetchGetAllChannelList,
+  fetchActivityList,
+  fetchDelActivity
+} from '@/service/api/djk-manage/activity';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+
+// const TypeName = ['企业用户', 'B端用户', 'C端用户'];
+// const statusList = ['上架', '下架'];
+const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    key: 'activityImg',
+    title: '活动封面',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return <NImage src={row.activityImg} class={'h90px w90px'}></NImage>;
+    }
+  },
+  {
+    key: 'activityName',
+    title: '活动名称',
+    align: 'center',
+    width: 100,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'activityType',
+    title: '活动类型',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'productAName',
+    title: '商品A',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'productBName',
+    title: '商品B',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+
+  {
+    key: 'sales',
+    title: '活动时间',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    },
+    render: (row: any) => {
+      return `${row.activityStartTime} ~ ${row.activityEndTime}`;
+    }
+  },
+  {
+    key: 'activityStatus',
+    title: '活动状态',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    },
+    render: (row: any) => {
+      let status = '';
+      switch (row.activityStatus) {
+        case 1:
+          status = '未开始';
+          break;
+        case 2:
+          status = '进行中';
+          break;
+        case 3:
+          status = '已结束';
+          break;
+        default:
+          status = '未知状态';
+          break;
+      }
+      return status;
+    }
+  },
+  {
+    key: 'price',
+    title: '领取/库存',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    },
+    render: (row: any) => {
+      return `${row.quantityClaimed}/${row.inventory}`;
+    }
+  },
+  {
+    key: 'shelfStatus',
+    title: '状态',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return <NSwitch uncheckedValue={0} checkedValue={1} value={row.shelfStatus} disabled></NSwitch>;
+    }
+  }
+];
+
+const [registerTable, { refresh }] = useTable({
+  searchFormConfig: {
+    schemas: [
+      {
+        label: '活动名称',
+        component: 'NInput',
+        field: 'activityName'
+      },
+      {
+        label: '状态',
+        field: 'activityStatus',
+        component: 'NSelect',
+        componentProps: {
+          options: [
+            {
+              label: '未开始',
+              value: 1
+            },
+            {
+              label: '进行中',
+              value: 2
+            },
+            {
+              label: '已结束',
+              value: 3
+            }
+          ]
+        }
+      }
+    ],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    opWdith: 160,
+    keyField: 'skuId',
+    title: '活动列表',
+    showAddButton: true,
+    scrollX: 1800,
+    fieldMapToTime: [
+      ['price', ['minPrice', 'maxPrice']],
+      ['createTime', ['startTime', 'endTime']]
+    ]
+  }
+});
+
+function handleAdd() {
+  router.push({
+    path: '/djk-manage/edit-activity'
+  });
+}
+function handleDetail(row: any) {
+  router.push({
+    path: '/djk-manage/edit-activity',
+    query: {
+      id: row.id,
+      mode: 'detail'
+    }
+  });
+}
+function handleEdit(row: any) {
+  console.log('edit', row);
+  router.push({
+    path: '/djk-manage/edit-activity',
+    query: {
+      id: row.id,
+      mode: 'edit'
+    }
+  });
+}
+
+function handleDelete(row: any) {
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要删除吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelActivity(row.id as string);
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchActivityList" @add="handleAdd" @register="registerTable">
+      <template #op="{ row }">
+        <NSpace>
+          <NButton size="small" @click="handleDetail(row)">商品详情</NButton>
+          <NButton size="small" @click="handleEdit(row)">编辑</NButton>
+          <NButton size="small" @click="handleDelete(row)">删除</NButton>
+        </NSpace>
+      </template>
+    </ZTable>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 413 - 0
src/views/djk-manage/edit-activity/index.vue

@@ -0,0 +1,413 @@
+<script setup lang="tsx">
+import { nextTick, onMounted, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import type { DataTableColumns, FormInst, FormRules } from 'naive-ui';
+import { NForm, NImage, NInput, useMessage } from 'naive-ui';
+import dayjs from 'dayjs';
+import { router } from '@/router';
+import { fetchActivityDetail, fetchAddActivity, fetchEditActivity } from '@/service/api/djk-manage/activity';
+import { fetchProductList } from '@/service/api/goods-center/health-goods';
+import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import type { FormSchema } from '@/components/zt/Form/types/form';
+import ZUpload from '../../../components/zt/upload/z-upload.vue';
+const current = ref('A');
+const route = useRoute();
+const formRef = ref<FormInst | null>(null);
+const message = useMessage();
+const disabled = ref(false);
+const useTime = ref<[string, string]>([dayjs().format('YYYY-MM-DD HH:mm:ss'), dayjs().format('YYYY-MM-DD HH:mm:ss')]);
+const loading = ref(false);
+const options = [
+  { label: '新用户专享', value: 1 },
+  { label: '节日活动', value: 2 },
+  { label: '用户召回', value: 3 },
+  { label: '会员活动', value: 4 },
+  { label: '其他', value: 5 }
+];
+// const userOptions = [
+//   { label: '全部用户', value: 1 },
+//   { label: '仅新用户', value: 2 },
+//   { label: '特定用户标签', value: 3 },
+//   { label: '指定用户', value: 4 }
+// ];
+const welfareGoodList = ref<any>([]);
+const productAInfo = ref<any>();
+const productBInfo = ref<any>();
+const model = ref({
+  activityName: '',
+  activityImg: '',
+  activityType: null as number | null,
+  activityStartTime: '',
+  activityEndTime: '',
+  activityUser: 0,
+  claimLimit: 0 as number | null,
+  shelfStatus: 0,
+  claimNum: 0,
+  inventory: 0,
+  productA: 0,
+  productB: 0,
+  createTime: '',
+  updateTime: '',
+  deleted: 0,
+  activityStatus: 0,
+  welfareGoodList: [] as any[]
+});
+
+const rules: FormRules = {
+  activityName: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  activityImg: {
+    type: 'string',
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  activityUser: {
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  activityType: {
+    type: 'number',
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  claimNum: {
+    min: 1,
+    max: 100,
+    type: 'number',
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入1-100的数字'
+  },
+
+  useStartTime: {
+    type: 'string',
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  productA: {
+    type: 'number',
+    required: true,
+    min: 1,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  productB: {
+    type: 'number',
+    required: true,
+    min: 1,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+  inventory: {
+    type: 'number',
+    required: true,
+    min: 1,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  }
+};
+
+interface ProductItem {
+  goodsImg: string;
+  goodsName: string;
+  goodsCode: string;
+  name?: string;
+}
+
+const columns: DataTableColumns<ProductItem> = [
+  {
+    type: 'selection',
+    multiple: false,
+    disabled(row: any) {
+      return row.name === 'Edward King 3';
+    }
+  },
+  {
+    key: 'goodsImg',
+    title: '商品封面',
+    align: 'center',
+    width: 120,
+    render(row: any) {
+      return <NImage src={row.goodsImg} class={'h90px w90px'}></NImage>;
+    }
+  },
+  {
+    key: 'goodsName',
+    title: '商品名称',
+    align: 'center'
+  },
+  {
+    key: 'goodsCode',
+    title: '商品编码',
+    align: 'center'
+  }
+];
+
+const searchSchemas: FormSchema[] = [
+  {
+    label: '门店名称',
+    component: 'ApiSelect',
+    field: 'shopId',
+    componentProps: {
+      api: fetchGetStoreList,
+      resultFeild: 'data.list',
+      labelFeild: 'shopName',
+      valueFeild: 'shopId'
+    }
+  },
+  {
+    label: '商品名称',
+    component: 'NInput',
+    field: 'goodsName'
+  },
+  {
+    label: '商品编码',
+    component: 'NInput',
+    field: 'goodsCode'
+  }
+];
+
+const [registerModalTable, { refresh, getTableCheckedRowKeys, getTableData }] = useTable({
+  searchFormConfig: {
+    schemas: searchSchemas,
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'id',
+    title: '商品列表',
+    showAddButton: false,
+    minHeight: 400,
+    defaultParamsNotReset: 'channelIdList'
+  }
+});
+
+const [registerModal, { openModal, closeModal }] = useModal({
+  title: '商品选择',
+  height: 800,
+  width: 1200
+});
+
+function handleValidateButtonClick(e: MouseEvent) {
+  e.preventDefault();
+  formRef.value?.validate(async errors => {
+    if (!errors) {
+      loading.value = true;
+      model.value.activityStartTime = useTime.value[0];
+      model.value.activityEndTime = useTime.value[1];
+      if (model.value.claimLimit === 0) {
+        model.value.claimLimit = null;
+      }
+      let res;
+      if (route.query.mode == 'edit') {
+        res = await fetchEditActivity(model.value);
+      } else {
+        res = await fetchAddActivity(model.value);
+      }
+      loading.value = false;
+      console.log(res);
+
+      if (res.data === null) {
+        router.push({
+          path: '/djk-manage/activity'
+        });
+      }
+    } else {
+      console.log(errors);
+      message.error('验证失败');
+    }
+  });
+}
+
+async function getDetail() {
+  // 获取详情接口
+  const res = await fetchActivityDetail(route.query.id);
+  if (res.data) {
+    model.value = res.data;
+    welfareGoodList.value = res.data.welfareGoodList;
+    productAInfo.value = welfareGoodList.value.find((item: any) => item.id === res.data.productA);
+    productBInfo.value = welfareGoodList.value.find((item: any) => item.id === res.data.productB);
+    useTime.value = [res.data.activityStartTime, res.data.activityEndTime];
+    console.log(useTime.value);
+  }
+}
+
+async function handleAdd(type: string) {
+  current.value = type;
+  openModal();
+  await nextTick();
+  refresh();
+}
+function choose() {
+  const keys = getTableCheckedRowKeys();
+  const data = getTableData();
+  closeModal();
+  if (current.value === 'A') {
+    model.value.productA = keys[0] as unknown as number;
+    productAInfo.value = data.find((item: any) => item.id === keys[0]);
+    model.value.welfareGoodList.push(productAInfo.value);
+  } else {
+    model.value.productB = keys[0] as unknown as number;
+    productBInfo.value = data.find((item: any) => item.id === keys[0]);
+    model.value.welfareGoodList.push(productBInfo.value);
+  }
+  console.log(keys, '选择的商品', productBInfo.value, data);
+}
+
+function handleDelete(type: string) {
+  if (type === 'A') {
+    model.value.welfareGoodList = model.value.welfareGoodList.filter((item: any) => item.id !== model.value.productA);
+    model.value.productA = 0;
+    productAInfo.value = null;
+  } else {
+    model.value.welfareGoodList = model.value.welfareGoodList.filter((item: any) => item.id !== model.value.productB);
+    model.value.productB = 0;
+    productBInfo.value = null;
+  }
+}
+onMounted(() => {
+  if (route.query.id) {
+    if (route.query.mode === 'detail') {
+      disabled.value = true;
+    }
+    getDetail();
+  }
+  console.log(route.query);
+});
+</script>
+
+<template>
+  <div class="edit-health-goods pl-20px pt-20px">
+    <NForm
+      ref="formRef"
+      :model="model"
+      :rules="rules"
+      label-placement="left"
+      label-width="auto"
+      require-mark-placement="right-hanging"
+      size="medium"
+      class="max-w-640px"
+      :disabled="disabled"
+    >
+      <NFormItem label="活动名称" path="activityName">
+        <NInput v-model:value="model.activityName" placeholder="" />
+      </NFormItem>
+      <NFormItem label="活动封面" path="activityImg">
+        <ZUpload v-model:value="model.activityImg" :max="1"></ZUpload>
+      </NFormItem>
+      <NFormItem label="活动类型" path="activityType">
+        <NSelect v-model:value="model.activityType" placeholder="" :options="options" />
+      </NFormItem>
+      <NFormItem label="可用时间">
+        <NDatePicker
+          v-model:formatted-value="useTime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          type="datetimerange"
+          clearable
+        />
+      </NFormItem>
+      <!--
+ <NFormItem label="参与用户" path="activityType">
+        <n-select v-model:value="model.activityType" placeholder="" :options="userOptions"  />
+      </NFormItem>
+-->
+      <NFormItem label="领取限制" :show-feedback="false">
+        <NGrid :cols="3" :x-gap="6">
+          <NFormItemGi>
+            <NRadioGroup v-model:value="model.claimLimit" name="radiogroup2">
+              <NRadio :value="0">不限制</NRadio>
+              <NRadio :value="1">限制</NRadio>
+            </NRadioGroup>
+          </NFormItemGi>
+          <NFormItemGi v-if="model.claimLimit === 1" path="claimNum">
+            <div class="flex items-center whitespace-nowrap">
+              每个用户限领
+              <NInputNumber v-model:value="model.claimNum" />
+              次
+            </div>
+          </NFormItemGi>
+        </NGrid>
+      </NFormItem>
+
+      <NFormItem label="商品A" path="productA">
+        <NButton v-if="!model.productA" icon-placement="left" secondary strong @click="handleAdd('A')">
+          <template #icon>
+            <icon-material-symbols:add-circle class="text-icon" />
+          </template>
+          添加商品
+        </NButton>
+        <div v-else class="relative flex border-1px rounded-8px p-10px">
+          <NImage :src="productAInfo.goodsImg" class="h90px w90px"></NImage>
+          <div class="flex-1">
+            <div>{{ productAInfo.goodsName }}</div>
+            <div>商品编码:{{ productAInfo.goodsCode }}</div>
+            <div>商品价格:¥{{ productAInfo.price }}</div>
+          </div>
+          <NButton class="absolute bottom-10px right-10px" text @click="handleDelete('A')">删除</NButton>
+        </div>
+      </NFormItem>
+      <NFormItem label="商品B" path="productB">
+        <NButton v-if="!model.productB" icon-placement="left" secondary strong @click="handleAdd('B')">
+          <template #icon>
+            <icon-material-symbols:add-circle class="text-icon" />
+          </template>
+          添加商品
+        </NButton>
+        <div v-else class="relative flex border-1px rounded-8px p-10px">
+          <NImage :src="productBInfo.goodsImg" class="h90px w90px"></NImage>
+          <div class="flex-1">
+            <div>{{ productBInfo.goodsName }}</div>
+            <div>商品编码:{{ productBInfo.goodsCode }}</div>
+            <div>商品价格:¥{{ productBInfo.price }}</div>
+          </div>
+          <NButton class="absolute bottom-10px right-10px" text @click="handleDelete('B')">删除</NButton>
+        </div>
+      </NFormItem>
+      <NFormItem label="库存" path="inventory">
+        <NInputNumber v-model:value="model.inventory" />
+      </NFormItem>
+      <div class="flex justify-end">
+        <NButton round type="primary" :loading="loading" @click="handleValidateButtonClick">保存</NButton>
+      </div>
+    </NForm>
+
+    <!--
+ <BasicModal @register="registerModal">
+      <NDataTable :columns="columns" :data="data" size="small" remote class="sm:h-full" />
+    </BasicModal>
+-->
+
+    <BasicModal @register="registerModal" @ok="choose">
+      <LayoutTable>
+        <ZTable
+          :immediate="false"
+          :show-table-action="false"
+          :columns="columns"
+          :api="fetchProductList"
+          :default-params="{ productStatus: 1 }"
+          @register="registerModalTable"
+        ></ZTable>
+      </LayoutTable>
+    </BasicModal>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+::v-deep .n-form-item-blank {
+  z-index: 6 !important;
+}
+.edit-health-goods {
+  background: #fff;
+}
+</style>

+ 218 - 3
src/views/goods-center/edit-health-goods/index.vue

@@ -1,7 +1,222 @@
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import type { FormInst, FormRules } from 'naive-ui';
+import { NForm, NInput, NSwitch, useMessage } from 'naive-ui';
+import E from 'wangeditor';
+import { router } from '@/router';
+import { fetchAddGoods, fetchEditGoods, fetchGoodsDetail } from '@/service/api/goods-center/health-goods';
+import ZUpload from '../../../components/zt/upload/z-upload.vue';
+const editor = ref();
+
+const route = useRoute();
+const formRef = ref<FormInst | null>(null);
+const message = useMessage();
+const disabled = ref(false);
+const useTime = ref<[string, string]>(['2026-01-06 00:00:00', '2026-01-06 23:59:59']);
+const loading = ref(false);
+const model = ref({
+  createTime: '',
+  updateTime: '',
+  shopId: 0,
+  goodsName: '',
+  goodsImg: '',
+  goodsCode: '',
+  price: 0,
+  shelfStatus: 0,
+  effectiveTime: 0,
+  advanceBookingTime: 0,
+  useStartTime: '00:00:00',
+  useEndTime: '00:00:00',
+  usageProcess: '',
+  buyNotice: '',
+  content: '',
+  shopName: '',
+  tel: '',
+  sales: 0
+});
+
+const rules: FormRules = {
+  shopName: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  goodsName: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  goodsImg: {
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  },
+
+  price: {
+    type: 'number',
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  effectiveTime: {
+    type: 'number',
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  advanceBookingTime: {
+    type: 'number',
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+
+  usageProcess: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  buyNotice: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  content: {
+    required: true,
+    trigger: ['blur', 'input'],
+    message: '请输入'
+  },
+  useStartTime: {
+    type: 'string',
+    required: true,
+    trigger: ['blur', 'change'],
+    message: '请输入'
+  }
+};
+
+function handleValidateButtonClick(e: MouseEvent) {
+  e.preventDefault();
+  formRef.value?.validate(async errors => {
+    if (!errors) {
+      loading.value = true;
+      model.value.content = editor.value.txt.html();
+      model.value.useStartTime = useTime.value[0];
+      model.value.useEndTime = useTime.value[1];
+      let res;
+      if (route.query.mode == 'edit') {
+        res = await fetchEditGoods(model.value);
+      } else {
+        res = await fetchAddGoods(model.value);
+      }
+      loading.value = false;
+      console.log(res);
+
+      if (res.data === null) {
+        router.push({
+          path: '/goods-center/health-goods'
+        });
+      }
+    } else {
+      console.log(errors);
+      message.error('验证失败');
+    }
+  });
+}
+
+async function getDetail() {
+  // 获取详情接口
+  const res = await fetchGoodsDetail(route.query.id);
+  if (res.data) {
+    model.value = res.data;
+    editor.value.txt.html(res.data.content); // 重新设置编辑器内容
+    useTime.value = [res.data.useStartTime, res.data.useEndTime];
+    console.log(useTime.value);
+  }
+}
+
+onMounted(() => {
+  editor.value = new E('#div1');
+  editor.value.create();
+  if (route.query.id) {
+    if (route.query.mode === 'detail') {
+      disabled.value = true;
+      editor.value.disable();
+    }
+    getDetail();
+  }
+  console.log(route.query);
+});
+</script>
 
 <template>
-  <div></div>
+  <div class="edit-health-goods pl-20px pt-20px">
+    <NForm
+      ref="formRef"
+      :model="model"
+      :rules="rules"
+      label-placement="left"
+      label-width="auto"
+      require-mark-placement="right-hanging"
+      size="medium"
+      class="max-w-640px"
+      :disabled="disabled"
+    >
+      <NFormItem label="门店名称" path="shopName">
+        <NInput v-model:value="model.shopName" placeholder="" />
+      </NFormItem>
+      <NFormItem label="商品名称" path="goodsName">
+        <NInput v-model:value="model.goodsName" placeholder="" />
+      </NFormItem>
+      <NFormItem label="封面" path="goodsImg">
+        <ZUpload :value="model.goodsImg" :max="1"></ZUpload>
+      </NFormItem>
+      <NFormItem label="商品价格" path="price">
+        <NInputNumber v-model:value="model.price" />
+      </NFormItem>
+      <NFormItem label="有效期" path="effectiveTime">
+        购买之日起
+        <NInputNumber v-model:value="model.effectiveTime" />
+        天有效
+      </NFormItem>
+      <NFormItem label="预约信息" path="advanceBookingTime">
+        提前
+        <NInputNumber v-model:value="model.advanceBookingTime" />
+        天预订
+      </NFormItem>
+      <NFormItem label="可用时间">
+        <NDatePicker
+          v-model:formatted-value="useTime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          type="datetimerange"
+          clearable
+        />
+      </NFormItem>
+      <NFormItem label="消费流程" path="usageProcess">
+        <NInput v-model:value="model.usageProcess" :maxlength="150" show-count placeholder="" type="textarea" />
+      </NFormItem>
+      <NFormItem label="购买人群" path="buyNotice">
+        <NInput v-model:value="model.buyNotice" :maxlength="150" show-count placeholder="" type="textarea" />
+      </NFormItem>
+      <NFormItem label="图文说明" path="content">
+        <div id="div1"></div>
+      </NFormItem>
+      <NFormItem label="上架状态" path="shelfStatus">
+        <NSwitch v-model:value="model.shelfStatus" :unchecked-value="0" :checked-value="1" />
+      </NFormItem>
+
+      <div class="flex justify-end">
+        <NButton round type="primary" :loading="loading" @click="handleValidateButtonClick">保存</NButton>
+      </div>
+    </NForm>
+  </div>
 </template>
 
-<style scoped></style>
+<style lang="scss" scoped>
+::v-deep .n-form-item-blank {
+  z-index: 6 !important;
+}
+.edit-health-goods {
+  background: #fff;
+}
+</style>

+ 31 - 12
src/views/goods-center/health-goods/index.vue

@@ -1,6 +1,7 @@
 <script setup lang="tsx">
 import { NButton, NImage, NSwitch } from 'naive-ui';
 // import dayjs from 'dayjs';
+import { router } from '@/router';
 import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
 import {
   // fetchGetAllChannelList,
@@ -15,14 +16,14 @@ const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
   {
     key: 'shopName',
     title: '门店名称',
-    align: 'left',
-    width: 200
+    align: 'center',
+    width: 100
   },
   {
     key: 'productNumber',
     title: '商品封面',
-    align: 'left',
-    width: 200,
+    align: 'center',
+    width: 120,
     render: (row: any) => {
       return <NImage src={row.goodsImg} class={'h90px w90px'}></NImage>;
     }
@@ -31,7 +32,7 @@ const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
     key: 'goodsName',
     title: '商品名称',
     align: 'center',
-    width: 120,
+    width: 100,
     ellipsis: {
       tooltip: true
     }
@@ -52,6 +53,9 @@ const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
     width: 120,
     ellipsis: {
       tooltip: true
+    },
+    render: (row: any) => {
+      return `购买后${row.effectiveTime}天有效`;
     }
   },
   {
@@ -147,9 +151,10 @@ const [registerTable, { refresh }] = useTable({
     isFull: false
   },
   tableConfig: {
+    opWdith: 160,
     keyField: 'skuId',
     title: '商品列表',
-    showAddButton: false,
+    showAddButton: true,
     scrollX: 1800,
     fieldMapToTime: [
       ['price', ['minPrice', 'maxPrice']],
@@ -158,12 +163,29 @@ const [registerTable, { refresh }] = useTable({
   }
 });
 
-function handleAdd() {}
+function handleAdd() {
+  router.push({
+    path: '/goods-center/edit-health-goods'
+  });
+}
 function handleDetail(row: any) {
-  console.log('edit', row);
+  router.push({
+    path: '/goods-center/edit-health-goods',
+    query: {
+      id: row.id,
+      mode: 'detail'
+    }
+  });
 }
 function handleEdit(row: any) {
   console.log('edit', row);
+  router.push({
+    path: '/goods-center/edit-health-goods',
+    query: {
+      id: row.id,
+      mode: 'edit'
+    }
+  });
 }
 
 function handleDelete(row: any) {
@@ -184,7 +206,7 @@ function handleDelete(row: any) {
 
 <template>
   <LayoutTable>
-    <ZTable :columns="columns" :api="fetchProductList" @register="registerTable">
+    <ZTable :columns="columns" :api="fetchProductList" @add="handleAdd" @register="registerTable">
       <template #op="{ row }">
         <NSpace>
           <NButton size="small" @click="handleDetail(row)">商品详情</NButton>
@@ -192,9 +214,6 @@ function handleDelete(row: any) {
           <NButton size="small" @click="handleDelete(row)">删除</NButton>
         </NSpace>
       </template>
-      <template #prefix="{}">
-        <NButton type="primary" @click="handleAdd">新增商品</NButton>
-      </template>
     </ZTable>
   </LayoutTable>
 </template>

+ 21 - 3
src/views/goods-center/store-goods/index.vue

@@ -21,12 +21,14 @@ import { commonExport } from '@/utils/common';
 import { useTable } from '@/components/zt/Table/hooks/useTable';
 import SvgIcon from '@/components/custom/svg-icon.vue';
 import { useModal } from '@/components/zt/Modal/hooks/useModal';
+// import { businessType } from '../../order-manage/normal-order/normal-order';
 type Price = { channelId: number | undefined; channelProdPrice: number; id: number; channelName?: string };
 const importTemplateRef = useTemplateRef('importTemplateRef');
 
 const options = ref<Api.goods.Channel[]>([]);
 // const TypeName = ['企业用户', 'B端用户', 'C端用户'];
 const statusList = ['商家下架', '上架', '违规下架', '平台审核'];
+const businessType = ref<Record<string, string>>({});
 const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
   {
     type: 'selection',
@@ -44,7 +46,7 @@ const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
         <div>
           <div>统一ID:</div>
           <div>
-            {row.businessType}
+            {/* {row.businessType} */}
             {row.supId}
           </div>
           <div>业务系统商品ID:</div>
@@ -73,12 +75,15 @@ const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
     }
   },
   {
-    key: 'channelCode',
+    key: 'businessType',
     title: '业务类型',
     align: 'center',
     width: 120,
     ellipsis: {
       tooltip: true
+    },
+    render: (row: any) => {
+      return <div>{businessType.value[row.businessType] || row.businessType}</div>;
     }
   },
   {
@@ -350,6 +355,17 @@ const tableData = computed(() => {
   return getTableData();
 });
 
+async function getrDic() {
+  const res = (await fetchGetDictDataList({ typeCode: 'sys_business_type' })) as any;
+  if (!res.error) {
+    res.data.list.forEach((item: any) => {
+      businessType.value[item.value] = item.name;
+    });
+    console.log('1111111111111111', businessType.value);
+  }
+}
+
+getrDic();
 async function handleSubmitImport(file: File) {
   const { error } = await fetchImportGoods({ file });
   if (!error) {
@@ -459,7 +475,9 @@ async function handleExport() {
   <LayoutTable>
     <ZTable :columns="columns" :api="fetchProductList" @register="registerTable">
       <template #op="{ row }">
-        <NButton size="small" ghost type="primary" @click="handleModalPrice(row)">设置渠道及价格</NButton>
+        <NButton v-if="row.businessType !== 'JY'" size="small" ghost type="primary" @click="handleModalPrice(row)">
+          设置渠道及价格
+        </NButton>
       </template>
       <template #prefix="{ loading }">
         <NSpace>

+ 141 - 0
src/views/government/claim-records/index.vue

@@ -0,0 +1,141 @@
+<script setup lang="tsx">
+import { computed } from 'vue';
+import { NButton } from 'naive-ui';
+// import dayjs from 'dayjs';
+// import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
+import { fetchGetChannelList, fetchList } from '@/service/api/government/claim-records';
+import { commonExport } from '@/utils/common';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+
+const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    key: 'id',
+    title: '领取流水号',
+    align: 'left',
+    width: 200
+  },
+  {
+    key: 'topChannelName',
+    title: '所属企业',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'userName',
+    title: '员工姓名',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'mobile',
+    title: '员工电话',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'receivePoints',
+    title: '领取积分',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'receiveTime',
+    title: '领取时间',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  }
+];
+
+const [registerTable, { getTableData, getSeachForm, setTableLoading }] = useTable({
+  searchFormConfig: {
+    schemas: [
+      {
+        label: '所属企业',
+        component: 'ApiSelect',
+        field: 'channelId',
+        componentProps: {
+          api: fetchGetChannelList,
+          resultFeild: 'data',
+          labelFeild: 'channelName',
+          valueFeild: 'id'
+        }
+      },
+      {
+        label: '员工姓名',
+        component: 'NInput',
+        field: 'userName'
+      },
+      {
+        label: '员工电话',
+        component: 'NInput',
+        field: 'mobile'
+      },
+
+      {
+        label: '领取时间',
+        component: 'NDatePicker',
+        field: 'createTime',
+        componentProps: {
+          type: 'datetimerange',
+          defaultTime: ['00:00:00', '23:59:59']
+        }
+      }
+    ],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'skuId',
+    title: '领取列表',
+    showAddButton: false,
+    scrollX: 1800,
+    fieldMapToTime: [['createTime', ['receiveStartTime', 'receiveEndTime']]]
+  }
+});
+
+const tableData = computed(() => {
+  return getTableData();
+});
+
+async function handleExport() {
+  setTableLoading(true);
+  try {
+    await commonExport('/smqjh-system/app-api/v1/claim/export', getSeachForm(), '领取列表.xlsx');
+  } finally {
+    setTableLoading(false);
+  }
+}
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchList" @register="registerTable">
+      <template #prefix="{ loading }">
+        <NSpace>
+          <NButton size="small" :disabled="tableData.length == 0" :loading="loading" @click="handleExport">
+            导出全部
+          </NButton>
+        </NSpace>
+      </template>
+    </ZTable>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 25 - 0
src/views/order-manage/normal-order/index.vue

@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router';
 import { NTag, useDialog } from 'naive-ui';
 import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
 import {
+  fetchAudit,
   fetchBreakDownload,
   fetchExportList,
   fetchExportOrderList,
@@ -219,6 +220,21 @@ function handleOrderDetail(row: Api.delivery.deliveryOrder) {
   });
   // orderMoadl.value?.open(String(row.orderNumber));
 }
+
+async function handleAudit(row: any) {
+  window.$dialog?.info({
+    title: '提示',
+    content: `此操作将核销该订单,且无法撤销,确定要核销吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchAudit({ orderNumber: row.orderNumber });
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
 async function getNums() {
   const form = getSeachForm();
   const params = {
@@ -335,6 +351,15 @@ watch(
     >
       <template #op="{ row }">
         <NButton size="small" type="primary" ghost @click="handleOrderDetail(row)">订单详情</NButton>
+        <NButton
+          v-if="row.businessType === 'DJK' && row.isWriteOff == 1 && row.hbOrderStatus != 80"
+          size="small"
+          type="primary"
+          ghost
+          @click="handleAudit(row)"
+        >
+          核销
+        </NButton>
       </template>
       <template #header>
         <div class="flex items-center">

+ 67 - 1
src/views/order-manage/normal-order/normal-order.ts

@@ -278,6 +278,71 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
   }
 ];
 
+export const orderDJKColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '商品',
+    key: 'goods',
+    width: 300,
+    render: row => {
+      const goodsNodes = [
+        h(NImage, { src: row.goodsImg, width: 80, height: 80 }),
+        h('div', { class: 'ml-2 ' }, [
+          h('div', { class: 'text-15px font-semibold' }, row.skuName),
+          h('div', { class: 'text-gray' }, `规格:${row.spec || '--'}`)
+        ])
+      ];
+      return h('div', { class: 'flex items-center' }, goodsNodes);
+    }
+  },
+  {
+    title: '单价(元)',
+    key: 'price',
+    width: 100
+  },
+  {
+    title: '数量',
+    key: 'goodsNum',
+    width: 250,
+    render: row => {
+      const nodes = [h('div', { class: 'mr-2' }, row.goodsNum)];
+      if (row.refundSuccessCount) {
+        nodes.push(h(NTag, { class: 'ml2', type: 'success' }, () => `退款成功:${row.refundSuccessCount}`));
+      }
+      if (row.refundIngCount && !row.refundSuccessCount) {
+        nodes.push(h(NTag, { class: 'ml2', type: 'error' }, () => `售后处理:${row.refundIngCount}`));
+      }
+      return h(NFlex, { align: 'center' }, () => nodes);
+    }
+  },
+  {
+    title: '小计/元',
+    key: 'productTotalAmount',
+    width: 100,
+    render: (row: any) => {
+      return h('div', {}, (row.goodsNum * 1000 * (row.price * 1000)) / 1000);
+    }
+  }
+];
+
+export const orderDJKLogColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '时间',
+    key: 'createTime'
+  },
+  {
+    title: '操作',
+    key: 'operation'
+  },
+  {
+    title: '操作员',
+    key: 'operator'
+  },
+  {
+    title: '备注',
+    key: 'remark'
+  }
+];
+
 export const deliveryInfo: FormSchema[] = [
   {
     label: '配送方式',
@@ -332,5 +397,6 @@ export const businessType = {
   CD: '充电',
   DYY: '电影演出',
   DJK: '大健康',
-  XNSP: '虚拟商品'
+  XNSP: '虚拟商品',
+  JY: '加油'
 };

+ 75 - 2
src/views/order-manage/order-detail/index.vue

@@ -11,6 +11,8 @@ import {
   businessType,
   chargeMethod,
   orderColumns,
+  orderDJKColumns,
+  orderDJKLogColumns,
   orderStatus,
   orderStatusEnum,
   yppStatus,
@@ -133,7 +135,7 @@ function secondsToTime(seconds: number) {
           <div>客户地址:{{ orderInfo?.consigneeAddress || '---' }}</div>
           <div>用户昵称:{{ orderInfo.userName || '---' }}</div>
           <div>用户电话:{{ orderInfo.consigneeMobile }}</div>
-          <div>企业身份:{{ orderInfo.level || '---' }}</div>
+          <div>企业身份:{{ orderInfo.channelName || '---' }}</div>
           <div>会员等级:{{ orderInfo.level || '---' }}</div>
           <div>分销等级:{{ orderInfo.level || '---' }}</div>
         </NCard>
@@ -175,7 +177,7 @@ function secondsToTime(seconds: number) {
             <div class="py-20px font-semibold">02 商品&费用</div>
             <NDataTable :columns="orderColumns" :data="orderInfo.orderItemList" :bordered="false" />
 
-            <NTable :single-line="false">
+            <NTable class="mt-20px" :single-line="false">
               <NThead>
                 <NTr>
                   <NTh>费用类型</NTh>
@@ -354,6 +356,77 @@ function secondsToTime(seconds: number) {
               </NTbody>
             </NTable>
           </template>
+          <template v-else-if="orderInfo.businessType == 'DJK'">
+            <div class="pb-20px font-semibold">基础信息</div>
+            <div>下单时间:{{ orderInfo.shopName || '暂无' }}</div>
+            <div>支付方式:{{ orderInfo.shopAddress || '暂无' }}</div>
+            <div>有效期:{{ orderInfo.tel || '暂无' }}</div>
+            <div class="py-20px font-semibold">订单信息</div>
+            <NDataTable :columns="orderDJKColumns" :data="[orderInfo.djkOrderAttachInfo]" :bordered="false" />
+            <div class="py-20px font-semibold">商品&费用</div>
+
+            <NTable class="mt-20px" :single-line="false">
+              <NThead>
+                <NTr>
+                  <NTh>费用类型</NTh>
+                  <NTh>金额/元</NTh>
+                </NTr>
+              </NThead>
+              <NTbody>
+                <NTr>
+                  <NTd>商品总额</NTd>
+                  <NTd>{{ orderInfo.total }}</NTd>
+                </NTr>
+                <NTr>
+                  <NTd>配送费(快递)</NTd>
+                  <NTd>{{ orderInfo.freightAmount }}</NTd>
+                </NTr>
+                <NTr>
+                  <NTd>积分</NTd>
+                  <NTd>-{{ (Number(orderInfo.offsetPoints) / 100).toFixed(2) || 0 }}</NTd>
+                </NTr>
+                <NTr>
+                  <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY">需付款</NTd>
+                  <NTd
+                    v-if="
+                      orderInfo.hbOrderStatus != orderStatusEnum.WAIT_PAY &&
+                      orderInfo.hbOrderStatus != orderStatusEnum.ORDER_CANCEL
+                    "
+                  >
+                    实际付款
+                  </NTd>
+                  <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL">应付款</NTd>
+                  <NTd>{{ orderInfo.actualTotal }}</NTd>
+                </NTr>
+              </NTbody>
+            </NTable>
+
+            <div class="py-20px font-semibold">操作记录</div>
+            <NDataTable :columns="orderDJKLogColumns" :data="orderInfo.djkOrderLogList" :bordered="false" />
+          </template>
+          <template v-else-if="orderInfo.businessType == 'JY'">
+            <div class="mt-20px pb-20px font-semibold">01 订单编号</div>
+            <div>我方订单编号:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>小桔订单编号:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+
+            <div class="mt-20px pb-20px font-semibold">02 油站信息</div>
+            <div>油站名称:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>油站地址:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>所在城市:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+
+            <div class="mt-20px pb-20px font-semibold">03 加油明细</div>
+            <div>油品名称:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>枪号:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>加油量:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+
+            <div class="mt-20px pb-20px font-semibold">04 加油明细</div>
+            <div>门店价:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>平台价:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>订单金额:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>抵扣金额:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>服务费:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+            <div>需付金额:{{ orderInfo.yppDetail?.movieName || '---' }}</div>
+          </template>
         </NCard>
       </div>
     </div>

+ 317 - 6
src/views/xsb-manage/store-info/index.vue

@@ -1,9 +1,28 @@
 <script setup lang="tsx">
-import { NCollapse, NCollapseItem, NTag } from 'naive-ui';
+import { ref } from 'vue';
+import { NButton, NCollapse, NCollapseItem, NInput, NSelect, NTag, NTimePicker } from 'naive-ui';
+import axios from 'axios';
 import { commonStatus } from '@/constants/business';
-import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
+import {
+  fetchAddShop,
+  fetchDelShop,
+  fetchEditShop,
+  fetchGetAreaList,
+  fetchGetShopCity,
+  fetchGetStoreList
+} from '@/service/api/xsb-manage/store-info';
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
 import { useTable } from '@/components/zt/Table/hooks/useTable';
 import { $t } from '@/locales';
+// 定义选项类型
+interface AreaOption {
+  label: string;
+  value: string;
+}
+
+// 第三方地图API配置(替换为你使用的地图API地址和密钥)
+const MAP_API_KEY = 'UNBBZ-OKDCB-FSTU4-N5EGG-CMJ67-CKB6H'; // 自行申请
+const MAP_API_URL = 'https://apis.map.qq.com/ws/geocoder/v1/';
 const columns: NaiveUI.TableColumn<Api.Store.ShopDetail>[] = [
   {
     key: 'hbStationNo',
@@ -101,8 +120,8 @@ const columns: NaiveUI.TableColumn<Api.Store.ShopDetail>[] = [
     }
   }
 ];
-
-const [registerTable] = useTable({
+const loading = ref(false);
+const [registerTable, { refresh }] = useTable({
   searchFormConfig: {
     schemas: [
       {
@@ -114,6 +133,33 @@ const [registerTable] = useTable({
         field: 'hbStationNo',
         label: '商家门店编码',
         component: 'NInput'
+      },
+      {
+        field: 'city',
+        label: '城市',
+        component: 'ApiSelect',
+        componentProps: {
+          api: fetchGetShopCity,
+          labelField: 'city',
+          valueField: 'city'
+        }
+      },
+      {
+        field: 'runStatus',
+        label: '经营状态',
+        component: 'NSelect',
+        componentProps: {
+          options: [
+            {
+              label: '停业',
+              value: 0
+            },
+            {
+              label: '营业',
+              value: 1
+            }
+          ]
+        }
       }
     ],
     inline: false,
@@ -124,14 +170,279 @@ const [registerTable] = useTable({
   tableConfig: {
     keyField: 'id',
     title: '门店信息',
-    showAddButton: false
+    opWdith: 160
+  }
+});
+
+const areaOptions = ref<AreaOption[]>([]);
+
+const businessTimeList = ref<any[]>([
+  {
+    startTime: '09:00',
+    endTime: '18:00'
+  }
+]);
+const [registerModalForm, { openModal, setFieldsValue, getFieldsValue, closeModal, setSubLoading }] = useModalFrom({
+  modalConfig: {
+    title: '门店',
+    isShowHeaderText: true
+  },
+  formConfig: {
+    schemas: [
+      {
+        field: 'shopId',
+        component: 'NInput',
+        label: 'shopId',
+        show: false
+      },
+
+      {
+        field: 'shopName',
+        component: 'NInput',
+        label: '门店名称',
+        required: true
+      },
+      {
+        field: 'shopLogo',
+        component: 'zUpload',
+        label: '封面图标',
+        required: true,
+        componentProps: {
+          max: 1
+        }
+      },
+      {
+        label: '所属区域',
+        field: 'area',
+        required: true,
+        component: 'NSelect',
+        componentProps: {
+          options: areaOptions.value,
+          remote: true,
+          filterable: true
+        },
+        render: ({ model }) => {
+          return (
+            <NSelect
+              v-model:value={model.area}
+              filterable
+              placeholder=""
+              options={areaOptions.value}
+              clearable
+              remote
+            />
+          );
+        }
+      },
+      {
+        field: 'shopAddress',
+        component: 'NInput',
+        label: '详细地址',
+        required: true
+      },
+      {
+        field: 'shopLng',
+        component: 'NInput',
+        label: '详细地址',
+        required: true,
+        show: false
+      },
+      {
+        field: 'shopLat',
+        label: '门店坐标',
+        component: 'NInput',
+        render: ({ model }) => {
+          return (
+            <div class={'flex items-center'}>
+              <NInput v-model:value={model.shopLat} />
+              <NInput class={'ml-10px'} v-model:value={model.shopLng} />
+              <NButton class={'ml-10px'} text onClick={() => handleReverves()}>
+                按地址生成
+              </NButton>
+            </div>
+          );
+        }
+      },
+      {
+        field: 'tel',
+        component: 'NInput',
+        label: '客服电话',
+        required: true
+      },
+      {
+        field: 'runStatus',
+        component: 'NSwitch',
+        label: '经营状态',
+        defaultValue: 1,
+        required: true,
+        componentProps: {
+          checkedValue: 1,
+          uncheckedValue: 0
+        }
+      },
+      {
+        field: 'businessTime',
+        component: 'NInput',
+        label: '营业时间',
+        render: _row => {
+          return (
+            <div>
+              {businessTimeList?.value.map((item: any, index) => {
+                return (
+                  <div class={'flex items-center'}>
+                    <NTimePicker v-model:formatted-value={item.startTime} format="HH:mm" value-format="HH:mm" />
+                    至
+                    <NTimePicker v-model:formatted-value={item.endTime} format="HH:mm" value-format="HH:mm" />
+                    <NButton class={'ml-10px'} type="primary" text onClick={() => addBusinessTime()}>
+                      添加
+                    </NButton>
+                    <NButton class={'ml-10px'} text type="primary" onClick={() => removeBusinessTime(index)}>
+                      删除
+                    </NButton>
+                  </div>
+                );
+              })}
+            </div>
+          );
+        }
+      }
+    ],
+    labelWidth: 120,
+    layout: 'horizontal',
+    gridProps: {
+      cols: '1',
+      itemResponsive: true
+    }
   }
 });
+
+async function handleReverves() {
+  const form = await getFieldsValue();
+  if (!form.area || !form.shopAddress) {
+    window.$message?.error('请先选择区域和地址');
+    return;
+  }
+  if (loading.value) return;
+  loading.value = true;
+
+  const res = await axios.get(MAP_API_URL, {
+    params: {
+      address: form.area + form.shopAddress, // 请求参数
+      key: MAP_API_KEY // API密钥
+    },
+    timeout: 10000, // 单独设置超时(第三方接口建议加长,避免网络问题)
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  });
+  loading.value = false;
+  console.log(res);
+
+  if (res.data.result) {
+    setFieldsValue({ shopLat: res.data.result.location.lat, shopLng: res.data.result.location.lng });
+  } else {
+    window.$message?.error('请输入正确的地址');
+  }
+}
+async function getArea() {
+  const res = await fetchGetAreaList({});
+  areaOptions.value =
+    res.data?.map((item: any) => {
+      return {
+        label: item,
+        value: item
+      };
+    }) || [];
+
+  console.log(11111111, areaOptions.value);
+}
+getArea();
+function addBusinessTime() {
+  if (businessTimeList.value.length >= 3) {
+    window.$message?.error('最多只能添加3条数据');
+    return;
+  }
+  businessTimeList.value.push({
+    startTime: '00:00',
+    endTime: '23:00'
+  });
+}
+
+function removeBusinessTime(index: number) {
+  if (businessTimeList.value.length <= 1) return;
+  businessTimeList.value.splice(index, 1);
+}
+
+function handleAdd() {
+  openModal();
+}
+function handleEdit(record: any) {
+  openModal(record);
+  setFieldsValue(record);
+  if (record.businessTime) {
+    businessTimeList.value = record.businessTime.split(',');
+    businessTimeList.value = businessTimeList.value.map((item: any) => {
+      const time = item.split('-');
+      return {
+        startTime: time[0],
+        endTime: time[1]
+      };
+    });
+  }
+}
+function handleDelete(row: any) {
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要删除吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelShop(row.shopId as string);
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+async function handleSubmit() {
+  const form = await getFieldsValue();
+  setSubLoading(true);
+  const businessTime: string[] = [];
+  businessTimeList.value.forEach(i => {
+    const item = `${i.startTime}-${i.endTime}`;
+    businessTime.push(item);
+  });
+  form.businessTime = businessTime.join();
+  form.province = form.area.split('-')[0];
+  form.city = form.area.split('-')[1];
+  console.log(form, '表单');
+  let res;
+  if (!form.shopId) {
+    res = await fetchAddShop(form);
+  } else {
+    res = await fetchEditShop(form);
+  }
+  setSubLoading(false);
+
+  if (res.response.data.code === '00000') {
+    closeModal();
+    refresh();
+  }
+}
 </script>
 
 <template>
   <LayoutTable>
-    <ZTable :columns="columns" :show-table-action="false" :api="fetchGetStoreList" @register="registerTable"></ZTable>
+    <ZTable :columns="columns" :api="fetchGetStoreList" @add="handleAdd" @register="registerTable">
+      <template #op="{ row }">
+        <NSpace v-if="row.isOperable">
+          <NButton type="primary" ghost size="small" @click="handleEdit(row)">编辑</NButton>
+          <NButton type="error" ghost size="small" @click="handleDelete(row)">删除</NButton>
+        </NSpace>
+      </template>
+    </ZTable>
+
+    <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit"></BasicModelForm>
   </LayoutTable>
 </template>