Procházet zdrojové kódy

```
feat(goods-center): 新增虚拟商品和健康商品管理模块

- 添加虚拟商品页面功能,包括商品列表展示、导入导出、价格设置等
- 添加健康商品页面功能,包含商品管理和操作功能
- 新增相关API接口文件和服务函数
- 配置路由和国际化语言支持
- 更新环境配置中的服务基础URL
```

wenjie před 1 dnem
rodič
revize
4fd53d0629

+ 6 - 3
.env.test

@@ -4,9 +4,12 @@
 # VITE_SERVICE_BASE_URL=http://89561bkaq794.vicp.fun:53846 #张
 # VITE_SERVICE_BASE_URL=http://192.168.1.89:8080#田
 # VITE_SERVICE_BASE_URL=http://192.168.0.19:8080 #邓
-# VITE_SERVICE_BASE_URL=http://d7b0f86.r36.cpolar.top #付
-VITE_SERVICE_BASE_URL=https://smqjh.api.zswlgz.com
-# VITE_SERVICE_BASE_URL=http://47.109.84.152:8081 #测试本地服务器
+# 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=http://47.109.84.152:8081 #打包测试本地服务器
 # VITE_SERVICE_BASE_URL=/plt #测试打包服务器
 
 # other backend service base url, test environment

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

@@ -275,7 +275,10 @@ const local: App.I18n.Schema = {
     'film-manage': '',
     'film-manage_config': '',
     'film-manage_film-list': '',
-    'film-manage_setprice': ''
+    'film-manage_setprice': '',
+    'goods-center_virtual-goods': '',
+    'goods-center_health-goods': '',
+    'goods-center_edit-health-goods': ''
   },
   page: {
     login: {

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

@@ -272,7 +272,10 @@ const local: App.I18n.Schema = {
     'film-manage': '',
     'film-manage_config': '',
     'film-manage_film-list': '',
-    'film-manage_setprice': ''
+    'film-manage_setprice': '',
+    'goods-center_virtual-goods': '',
+    'goods-center_health-goods': '',
+    'goods-center_edit-health-goods': ''
   },
   page: {
     login: {

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

@@ -25,8 +25,11 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   "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"),
+  "goods-center_edit-health-goods": () => import("@/views/goods-center/edit-health-goods/index.vue"),
+  "goods-center_health-goods": () => import("@/views/goods-center/health-goods/index.vue"),
   "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_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"),

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

@@ -115,6 +115,24 @@ export const generatedRoutes: GeneratedRoute[] = [
       i18nKey: 'route.goods-center'
     },
     children: [
+      {
+        name: 'goods-center_edit-health-goods',
+        path: '/goods-center/edit-health-goods',
+        component: 'view.goods-center_edit-health-goods',
+        meta: {
+          title: 'goods-center_edit-health-goods',
+          i18nKey: 'route.goods-center_edit-health-goods'
+        }
+      },
+      {
+        name: 'goods-center_health-goods',
+        path: '/goods-center/health-goods',
+        component: 'view.goods-center_health-goods',
+        meta: {
+          title: 'goods-center_health-goods',
+          i18nKey: 'route.goods-center_health-goods'
+        }
+      },
       {
         name: 'goods-center_store-goods',
         path: '/goods-center/store-goods',
@@ -132,6 +150,15 @@ export const generatedRoutes: GeneratedRoute[] = [
           title: 'goods-center_type-admin',
           i18nKey: 'route.goods-center_type-admin'
         }
+      },
+      {
+        name: 'goods-center_virtual-goods',
+        path: '/goods-center/virtual-goods',
+        component: 'view.goods-center_virtual-goods',
+        meta: {
+          title: 'goods-center_virtual-goods',
+          i18nKey: 'route.goods-center_virtual-goods'
+        }
       }
     ]
   },

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

@@ -189,8 +189,11 @@ const routeMap: RouteMap = {
   "film-manage_film-list": "/film-manage/film-list",
   "film-manage_setprice": "/film-manage/setprice",
   "goods-center": "/goods-center",
+  "goods-center_edit-health-goods": "/goods-center/edit-health-goods",
+  "goods-center_health-goods": "/goods-center/health-goods",
   "goods-center_store-goods": "/goods-center/store-goods",
   "goods-center_type-admin": "/goods-center/type-admin",
+  "goods-center_virtual-goods": "/goods-center/virtual-goods",
   "government": "/government",
   "government_government-list": "/government/government-list",
   "government_points": "/government/points",

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

@@ -0,0 +1,82 @@
+import { request } from '@/service/request';
+/**
+ * 分页获取商品
+ * @param params
+ * @returns
+ */
+export function fetchProductList(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/goods/goodsList',
+    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'
+  });
+}
+
+/**
+ *
+ * 删除商品
+ * @returns
+ */
+export function fetchDelGoods(id: string) {
+  return request({
+    url: `/smqjh-pms/api/v1/goods/${id}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 保存商品
+ * @returns
+ */
+export function fetchAddGoods(data: any) {
+  return request<Api.goods.Channel[]>({
+    url: '/smqjh-pms/api/v1/goods/addDJKGoods',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 保存商品
+ * @returns
+ */
+export function fetchEditGoods(data: any) {
+  return request<Api.goods.Channel[]>({
+    url: '/smqjh-pms/api/v1/goods/updateDJKGoods',
+    method: 'put',
+    data
+  });
+}
+
+/**
+ * 商品详情
+ * @returns
+ */
+export function fetchGoodsDetail(data: any) {
+  return request<Api.goods.Channel[]>({
+    url: '/smqjh-pms/api/v1/goods/info',
+    method: 'put',
+    data
+  });
+}

+ 75 - 0
src/service/api/goods-center/virtual-goods/index.ts

@@ -0,0 +1,75 @@
+import { request } from '@/service/request';
+/**
+ * 分页获取商品
+ * @param params
+ * @returns
+ */
+export function fetchProductList(data: any) {
+  return request({
+    url: '/smqjh-pms/v1/videoProduct/findByPage',
+    method: 'get',
+    params: data
+  });
+}
+
+/**
+ * 设置销售渠道及价格
+ * @param data
+ * @returns
+ */
+export function fetchSetUpChannels(data: any) {
+  return request<any>({
+    url: '/smqjh-pms/v1/videoProduct/saveOrUpdatePrice',
+    method: 'post',
+    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'
+  });
+}
+
+/**
+ *
+ * @param data 导入渠道商品数据
+ * @returns
+ */
+export function fetchImportGoods(data: { file: File }) {
+  return request({
+    url: 'smqjh-pms/v1/videoProduct/import',
+    method: 'post',
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  });
+}
+
+/**
+ * 修改商品状态
+ * @returns
+ */
+export function fetchChangeStatus(data: any) {
+  return request({
+    url: '/smqjh-pms/v1/videoProduct/updateProductStatus',
+    method: 'get',
+    params: data
+  });
+}

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

@@ -43,8 +43,11 @@ declare module "@elegant-router/types" {
     "film-manage_film-list": "/film-manage/film-list";
     "film-manage_setprice": "/film-manage/setprice";
     "goods-center": "/goods-center";
+    "goods-center_edit-health-goods": "/goods-center/edit-health-goods";
+    "goods-center_health-goods": "/goods-center/health-goods";
     "goods-center_store-goods": "/goods-center/store-goods";
     "goods-center_type-admin": "/goods-center/type-admin";
+    "goods-center_virtual-goods": "/goods-center/virtual-goods";
     "government": "/government";
     "government_government-list": "/government/government-list";
     "government_points": "/government/points";
@@ -164,8 +167,11 @@ declare module "@elegant-router/types" {
     | "film-manage_config"
     | "film-manage_film-list"
     | "film-manage_setprice"
+    | "goods-center_edit-health-goods"
+    | "goods-center_health-goods"
     | "goods-center_store-goods"
     | "goods-center_type-admin"
+    | "goods-center_virtual-goods"
     | "government_government-list"
     | "government_points"
     | "government_user-list"

+ 7 - 0
src/views/goods-center/edit-health-goods/index.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div></div>
+</template>
+
+<style scoped></style>

+ 202 - 0
src/views/goods-center/health-goods/index.vue

@@ -0,0 +1,202 @@
+<script setup lang="tsx">
+import { NButton, NImage, NSwitch } from 'naive-ui';
+// import dayjs from 'dayjs';
+import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
+import {
+  // fetchGetAllChannelList,
+  fetchDelGoods,
+  fetchProductList
+} from '@/service/api/goods-center/health-goods';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+
+// const TypeName = ['企业用户', 'B端用户', 'C端用户'];
+// const statusList = ['上架', '下架'];
+const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    key: 'shopName',
+    title: '门店名称',
+    align: 'left',
+    width: 200
+  },
+  {
+    key: 'productNumber',
+    title: '商品封面',
+    align: 'left',
+    width: 200,
+    render: (row: any) => {
+      return <NImage src={row.goodsImg} class={'h90px w90px'}></NImage>;
+    }
+  },
+  {
+    key: 'goodsName',
+    title: '商品名称',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'goodsCode',
+    title: '商品编码',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'effectiveTime',
+    title: '有效期',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'price',
+    title: '价格',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+
+  {
+    key: 'sales',
+    title: '销量',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'shelfStatus',
+    title: '状态',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return <NSwitch uncheckedValue={0} checkedValue={1} value={row.status} disabled></NSwitch>;
+    }
+  }
+];
+
+const [registerTable, { refresh }] = useTable({
+  searchFormConfig: {
+    schemas: [
+      {
+        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'
+      },
+
+      // {
+      //   label: '业务类型',
+      //   field: 'channelCode',
+      //   component: 'ApiSelect',
+      //   componentProps: {
+      //     api: fetchGetDictDataList,
+      //     labelFeild: 'name',
+      //     valueFeild: 'value',
+      //     resultFeild: 'data.list',
+      //     params: {
+      //       typeCode: 'sys_business_type'
+      //     }
+      //   }
+      // },
+      {
+        label: '状态',
+        field: 'productStatus',
+        component: 'NSelect',
+        componentProps: {
+          options: [
+            {
+              label: '下架',
+              value: 0
+            },
+            {
+              label: '上架',
+              value: 1
+            }
+          ]
+        }
+      }
+    ],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'skuId',
+    title: '商品列表',
+    showAddButton: false,
+    scrollX: 1800,
+    fieldMapToTime: [
+      ['price', ['minPrice', 'maxPrice']],
+      ['createTime', ['startTime', 'endTime']]
+    ]
+  }
+});
+
+function handleAdd() {}
+function handleDetail(row: any) {
+  console.log('edit', row);
+}
+function handleEdit(row: any) {
+  console.log('edit', row);
+}
+
+function handleDelete(row: any) {
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要删除吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchDelGoods(row.id as string);
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchProductList" @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>
+      <template #prefix="{}">
+        <NButton type="primary" @click="handleAdd">新增商品</NButton>
+      </template>
+    </ZTable>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 516 - 0
src/views/goods-center/virtual-goods/index.vue

@@ -0,0 +1,516 @@
+<script setup lang="tsx">
+import { computed, ref, useTemplateRef } from 'vue';
+import { NButton, NInputNumber, NSelect } from 'naive-ui';
+// import dayjs from 'dayjs';
+// import { fetchGetStoreList } from '@/service/api/xsb-manage/store-info';
+// import {
+// fetchGetAllChannelList
+// fetchImportGoods,
+// fetchSetUpChannels
+// } from '@/service/api/goods/store-goods';
+// import { fetchGetDictDataList } from '@/service/api/system-manage';
+import {
+  // fetchGetAllChannelList,
+  fetchChangeStatus,
+  fetchGetChannelList,
+  fetchImportGoods,
+  fetchProductList,
+  fetchSetUpChannels
+} from '@/service/api/goods-center/virtual-goods';
+import { areAllItemsAllFieldsFilled } from '@/utils/zt';
+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';
+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 columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    type: 'selection',
+    align: 'center',
+    width: 48,
+    fixed: 'left'
+  },
+  {
+    key: 'productNumber',
+    title: '商品ID',
+    align: 'left',
+    width: 200
+  },
+  {
+    key: 'productName',
+    title: '商品名称',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'productType',
+    title: '商品类型',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'businessType',
+    title: '业务类型',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  // {
+  //   key: 'shopName',
+  //   title: '商户',
+  //   align: 'center',
+  //   width: 120,
+  //   ellipsis: {
+  //     tooltip: true
+  //   },
+  //   render: (row: any) => {
+  //     return  '-';
+  //   }
+  // },
+  {
+    key: 'faceValue',
+    title: '面值(元)',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'purchasePrice',
+    title: '采购价',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'pmsVideoChannelPrices',
+    title: '价格',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return (
+        <div>
+          {row.pmsVideoChannelPrices.map((it: Api.government.ChannelVO) => {
+            return (
+              <div>
+                {it.channelName}:¥{it.price}
+              </div>
+            );
+          })}
+        </div>
+      );
+    }
+  },
+  {
+    key: 'inventory',
+    title: '库存',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'productStatus',
+    title: '状态',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return (
+        <div class="flex items-center justify-center">
+          <n-badge color={row.productStatus == 0 ? 'green' : 'red'} value={row.productStatus} dot />
+          <span class="ml-2">{statusList[row.productStatus]}</span>
+        </div>
+      );
+    }
+  },
+  {
+    key: 'updateTime',
+    title: '更新时间',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  }
+];
+const PriceColumns: NaiveUI.TableColumn<Price>[] = [
+  {
+    title: '销售渠道',
+    key: 'channelId',
+    align: 'center',
+    width: 250,
+    render: row => {
+      return (
+        <NSelect
+          options={options.value}
+          labelField="channelName"
+          valueField="id"
+          value={row.channelId}
+          clearable
+          onUpdate:value={value => {
+            options.value.map(it => {
+              if (it.id == row.channelId) {
+                it.disabled = false;
+              }
+              return it;
+            });
+            row.channelId = value ? Number(value) : undefined;
+            row.channelName = options.value.find(it => it.id == row.channelId)?.channelName;
+          }}
+          onUpdate:show={value => {
+            console.log('show', value);
+            options.value.map(it => {
+              if (it.id == row.channelId) {
+                if (value) {
+                  it.disabled = false;
+                }
+                if (!value) {
+                  it.disabled = true;
+                }
+              }
+              return it;
+            });
+          }}
+        ></NSelect>
+      );
+    }
+  },
+  {
+    title: '售价(元)',
+    key: 'channelProdPrice',
+    align: 'center',
+    width: 250,
+    render: row => {
+      return (
+        <NInputNumber
+          value={row.channelProdPrice}
+          precision={2}
+          onUpdate:value={value => {
+            row.channelProdPrice = Number(value);
+          }}
+          min={0}
+        />
+      );
+    }
+  },
+  {
+    title: () => {
+      return (
+        <div onClick={() => handleAddPrice()} class={'w-full flex items-center justify-center'}>
+          <SvgIcon
+            icon={'proicons:add-square'}
+            class={'cursor-pointer text-24px'}
+            style={'color:var(--n-color)'}
+          ></SvgIcon>
+        </div>
+      );
+    },
+    key: 'action',
+    width: 80,
+    align: 'center',
+    render: (_row, index) => {
+      return (
+        <div onClick={() => handleDelPrice(index)} class={'w-full flex items-center justify-center'}>
+          <SvgIcon
+            icon={'proicons:subtract-square'}
+            class={'cursor-pointer text-24px'}
+            style={'color:#f5222d'}
+          ></SvgIcon>
+        </div>
+      );
+    }
+  }
+];
+const PriceData = ref<Price[]>([]);
+const selectData = ref<Api.goods.ShopSku>();
+const [registerTable, { refresh, getTableData, getSeachForm, setTableLoading }] = useTable({
+  searchFormConfig: {
+    schemas: [
+      // {
+      //   label: '门店名称',
+      //   component: 'ApiSelect',
+      //   field: 'shopId',
+      //   componentProps: {
+      //     api: fetchGetStoreList,
+      //     resultFeild: 'data.list',
+      //     labelFeild: 'shopName',
+      //     valueFeild: 'shopId'
+      //   }
+      // },
+      {
+        label: '关键词',
+        component: 'NInput',
+        field: 'keywords'
+      },
+      // {
+      //   label: '业务类型',
+      //   field: 'channelCode',
+      //   component: 'ApiSelect',
+      //   componentProps: {
+      //     api: fetchGetDictDataList,
+      //     labelFeild: 'name',
+      //     valueFeild: 'value',
+      //     resultFeild: 'data.list',
+      //     params: {
+      //       typeCode: 'sys_business_type'
+      //     }
+      //   }
+      // },
+      {
+        label: '状态',
+        field: 'productStatus',
+        component: 'NSelect',
+        componentProps: {
+          options: [
+            {
+              label: '下架',
+              value: 1
+            },
+            {
+              label: '上架',
+              value: 0
+            }
+          ]
+        }
+      },
+      // {
+      //   label: '分类',
+      //   component: 'NCascader',
+      //   field: 'skuName',
+      //   componentProps: {}
+      // },
+      {
+        label: '更新时间',
+        component: 'NDatePicker',
+        field: 'createTime',
+        componentProps: {
+          type: 'datetimerange',
+          defaultTime: ['00:00:00', '23:59:59']
+        }
+      },
+      {
+        label: '价格范围',
+        component: 'NInput',
+        field: 'price',
+        componentProps: {
+          separator: '-',
+          pair: true,
+          placeholder: ['最低价', '最高价'],
+          allowInput: (value: string) => !value || /^\d+$/.test(value)
+        }
+      }
+    ],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'skuId',
+    title: '商品列表',
+    showAddButton: false,
+    scrollX: 1800,
+    fieldMapToTime: [
+      ['price', ['minPrice', 'maxPrice']],
+      ['createTime', ['startTime', 'endTime']]
+    ]
+  }
+});
+
+const [
+  registerModalPrice,
+  { openModal: openPriceModal, setSubLoading: setSubModalLoding, closeModal: closePriceModal }
+] = useModal({
+  title: '设置渠道及价格',
+  width: 800,
+  height: 300
+});
+
+// const isDisabledExport = computed(() => {
+//   return !getTableCheckedRowKeys().length;
+// });
+const tableData = computed(() => {
+  return getTableData();
+});
+
+async function handleSubmitImport(file: File) {
+  const { error } = await fetchImportGoods({ file });
+  if (!error) {
+    importTemplateRef.value?.closeModal();
+  }
+  importTemplateRef.value?.setSubLoading(false);
+}
+function openImportModal() {
+  importTemplateRef.value?.openModal();
+}
+function handleModalPrice(row: Api.goods.ShopSku) {
+  selectData.value = row;
+
+  if (row.pmsVideoChannelPrices) {
+    PriceData.value = row.pmsVideoChannelPrices?.map((it: Api.government.ChannelVO) => {
+      options.value.map(its => {
+        if (its.id == it.channelId) {
+          its.disabled = true;
+        }
+        return its;
+      });
+
+      return {
+        channelName: it.channelName,
+        channelId: Number(it.channelId),
+        channelProdPrice: Number(it.price),
+        id: it.id
+      };
+    });
+  }
+
+  openPriceModal();
+}
+function handleAddPrice() {
+  // if (PriceData.value.length == 3) {
+  //   window.$message?.error('最多只能添加3条数据');
+  //   return;
+  // }
+  PriceData.value.push({
+    channelName: '',
+    channelId: undefined,
+    channelProdPrice: 1
+    // id: dayjs().valueOf()
+  });
+  console.log(PriceData.value);
+}
+function handleDelPrice(index: number) {
+  // PriceData.value = PriceData.value.filter(item => item.id != id);
+  PriceData.value.splice(index, 1);
+  console.log('删除', index, PriceData.value);
+}
+
+async function handleSubmitPrice() {
+  console.log(PriceData.value);
+
+  if (!PriceData.value.length) {
+    window.$message?.error('最少填写一条数据');
+    return;
+  }
+  if (!areAllItemsAllFieldsFilled(PriceData.value)) {
+    window.$message?.error('请填写完整数据');
+    return;
+  }
+  setSubModalLoding(true);
+  console.log(PriceData.value);
+
+  const form = {
+    // shopId: selectData.value?.shopId,
+    videoProductId: selectData.value?.id,
+    // purchasePrice: selectData.value?.channelProdList?.length ? selectData.value.channelProdList[0].purchasePrice : null,
+    // deliveryPrice: selectData.value?.channelProdList?.length ? selectData.value.channelProdList[0].deliveryPrice : null,
+    purchasePrice: selectData.value?.pmsVideoChannelPrices?.length
+      ? selectData.value.pmsVideoChannelPrices[0].purchasePrice
+      : null,
+    deliveryPrice: selectData.value?.pmsVideoChannelPrices?.length
+      ? selectData.value.pmsVideoChannelPrices[0].deliveryPrice
+      : null,
+    videoChannelPriceForms: PriceData.value.map(item => {
+      return {
+        // channelName: item.channelName,
+        id: item?.id,
+        channelId: item.channelId,
+        price: item.channelProdPrice
+      };
+    })
+  };
+  const { error } = await fetchSetUpChannels(form);
+  if (!error) {
+    closePriceModal();
+    refresh();
+    PriceData.value = [];
+    options.value.map(it => (it.disabled = false));
+  } else {
+    setSubModalLoding(false);
+  }
+}
+async function getData() {
+  const { data, error } = await fetchGetChannelList();
+  if (!error) {
+    options.value = data;
+  }
+}
+getData();
+async function handleExport() {
+  setTableLoading(true);
+  try {
+    await commonExport('/smqjh-pms/v1/videoProduct/export', getSeachForm(), '虚拟商品列表.xlsx');
+  } finally {
+    setTableLoading(false);
+  }
+}
+
+function handleStatus(row: any) {
+  window.$dialog?.info({
+    title: '提示',
+    content: `你确定要该操作吗?`,
+    positiveText: '确定',
+    negativeText: '取消',
+    onPositiveClick: async () => {
+      const { error } = await fetchChangeStatus({ id: row.id });
+      if (!error) {
+        refresh();
+      }
+    }
+  });
+}
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchProductList" @register="registerTable">
+      <template #op="{ row }">
+        <NSpace align="center">
+          <NButton size="small" ghost type="primary" @click="handleModalPrice(row)">设置渠道及价格</NButton>
+          <NButton size="small" ghost type="primary" @click="handleStatus(row)">
+            {{ row.productStatus == 1 ? '上架' : '下架' }}
+          </NButton>
+        </NSpace>
+      </template>
+      <template #prefix="{ loading }">
+        <NSpace>
+          <NButton size="small" @click="openImportModal">导入商品销售渠道及价格</NButton>
+          <NButton size="small" :disabled="tableData.length == 0" :loading="loading" @click="handleExport">
+            导出全部
+          </NButton>
+          <!-- <NButton size="small" :disabled="isDisabledExport">导出选中数据</NButton> -->
+          <!-- <NButton size="small">修改记录</NButton> -->
+        </NSpace>
+      </template>
+    </ZTable>
+    <ZImportTemplate
+      ref="importTemplateRef"
+      url="/smqjh-pms/v1/videoProduct/exportTemplate"
+      template-text="虚拟商品渠道及价格导入模版.xlsx"
+      modal-text="导入虚拟商品销售渠道及价格"
+      @submit="handleSubmitImport"
+    ></ZImportTemplate>
+    <BasicModal @register="registerModalPrice" @ok="handleSubmitPrice">
+      <NDataTable :columns="PriceColumns" :data="PriceData" :row-key="row => row.channelId" />
+    </BasicModal>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 14 - 5
src/views/order-manage/normal-order/index.vue

@@ -17,7 +17,7 @@ import { commonExport } from '@/utils/common';
 import { useModal } from '@/components/zt/Modal/hooks/useModal';
 import { useTable } from '@/components/zt/Table/hooks/useTable';
 // import { type } from '../../../../packages/axios/src/index';
-import { SearchForm, orderStatus } from './normal-order';
+import { SearchForm, businessType, orderStatus } from './normal-order';
 const router = useRouter();
 const activeTab = ref('all');
 const statusList = ref<{ label: string; value: string; num?: number }[]>([]);
@@ -36,7 +36,7 @@ const columns: NaiveUI.TableColumn<Api.delivery.deliveryOrder>[] = [
     align: 'center',
     width: 120,
     render: row => {
-      return <NTag class={'mt7'}>{row.businessType}</NTag>;
+      return <NTag>{businessType[row.businessType as keyof typeof businessType] || row.businessType}</NTag>;
     }
   },
   {
@@ -46,7 +46,7 @@ const columns: NaiveUI.TableColumn<Api.delivery.deliveryOrder>[] = [
     width: 220,
     render: row => {
       return (
-        <div class={'mt7'}>
+        <div>
           <div>
             {row.consigneeName}
             {row.consigneeMobile}
@@ -63,8 +63,17 @@ const columns: NaiveUI.TableColumn<Api.delivery.deliveryOrder>[] = [
     width: 120,
     render: row => {
       const statusKey = row.hbOrderStatus as keyof typeof orderStatus;
-      const statusText = orderStatus[statusKey] || '未知状态';
-      return <NTag class={'mt7'}>{statusText}</NTag>;
+      let statusText;
+      if (row.businessType == 'XNSP') {
+        if (row.hbOrderStatus == 60 || row.hbOrderStatus == 50 || row.hbOrderStatus == 0) {
+          statusText = '订单取消';
+        } else {
+          statusText = '订单完成';
+        }
+      } else {
+        statusText = orderStatus[statusKey] || '未知状态';
+      }
+      return <NTag>{statusText}</NTag>;
     }
   },
   {

+ 8 - 0
src/views/order-manage/normal-order/normal-order.ts

@@ -326,3 +326,11 @@ export const chargeMethod = [
   '连接器断开',
   '其它'
 ];
+
+export const businessType = {
+  XSB: '星闪豹',
+  CD: '充电',
+  DYY: '电影演出',
+  DJK: '大健康',
+  XNSP: '虚拟商品'
+};

+ 34 - 3
src/views/order-manage/order-detail/index.vue

@@ -8,6 +8,7 @@ import { fetchGetNomalOrderInfo } from '@/service/api/order-manage/normal-order'
 // import { useAppStore } from '@/store/modules/app';
 // import { copyTextToClipboard } from '@/utils/zt';
 import {
+  businessType,
   chargeMethod,
   orderColumns,
   orderStatus,
@@ -101,7 +102,9 @@ function secondsToTime(seconds: number) {
         </div>
         <NCard size="small" title="订单概览" :bordered="false">
           <div>订单编号:{{ orderInfo.orderNumber }}</div>
-          <div>业务类型:{{ orderInfo.businessType }}</div>
+          <div>
+            业务类型:{{ businessType[orderInfo.businessType as keyof typeof businessType] || orderInfo.businessType }}
+          </div>
           <div>
             订单状态:
             <template v-if="orderInfo.businessType != 'DYY'">
@@ -208,7 +211,7 @@ function secondsToTime(seconds: number) {
               </NTbody>
             </NTable>
           </template>
-          <template v-if="orderInfo.businessType == 'DYY'">
+          <template v-else-if="orderInfo.businessType == 'DYY'">
             <div class="pb-20px font-semibold">01 影片与场次信息</div>
             <div>影片名称:{{ orderInfo.yppDetail?.movieName || '暂无' }}</div>
             <div class="flex items-center justify-between">
@@ -277,7 +280,7 @@ function secondsToTime(seconds: number) {
               </template>
             </template>
           </template>
-          <template v-if="orderInfo.businessType == 'CD'">
+          <template v-else-if="orderInfo.businessType == 'CD'">
             <div class="flex flex-wrap items-center">
               <div class="mr10px flex-shrink-0">
                 <div>终端编号:{{ orderInfo.chargeOrder.connectorId || '--' }}</div>
@@ -323,6 +326,34 @@ function secondsToTime(seconds: number) {
               </NTable>
             </div>
           </template>
+          <template v-else-if="orderInfo.businessType == 'XNSP'">
+            <div>商品名称:{{ orderInfo.virtualOrderItem?.productName }}</div>
+            <div>充值账号:{{ orderInfo.virtualOrderItem?.rechargeAccount }}</div>
+            <NTable class="mt-10px" :single-line="true">
+              <NThead>
+                <NTr>
+                  <NTh>商品</NTh>
+                  <NTh>单价/元</NTh>
+                  <NTh>数量</NTh>
+                  <NTh>小计/元</NTh>
+                </NTr>
+              </NThead>
+              <NTbody>
+                <NTr>
+                  <NTd>{{ orderInfo.virtualOrderItem?.productName }}</NTd>
+                  <NTd>{{ orderInfo.virtualOrderItem?.sellPrice }}</NTd>
+                  <NTd>1</NTd>
+                  <NTd>{{ orderInfo.virtualOrderItem?.sellPrice }}</NTd>
+                </NTr>
+                <NTr v-if="orderInfo.offsetPoints">
+                  <NTd></NTd>
+                  <NTd></NTd>
+                  <NTd>积分抵扣</NTd>
+                  <NTd>-{{ Number(orderInfo.offsetPoints) || 0 }}</NTd>
+                </NTr>
+              </NTbody>
+            </NTable>
+          </template>
         </NCard>
       </div>
     </div>