Просмотр исходного кода

feat(film-manage): 新增电影管理模块功能

- 添加电影配置页面,包括倒计时、退改签协议、购票须知等功能
- 添加电影列表页面,支持按商户、关键词、状态筛选
- 添加电影价格设置页面
- 新增相关API接口和服务文件
- 在路由中注册新的电影管理路由

fix(table): 修复表格组件时间字段处理逻辑

- 实现通用时间字段处理函数handleCommonTime
- 优化getTableProps中的getSeachForm方法
- 修复时间范围查询的数据转换问题

feat(editor): 新增富文本编辑器组件

- 集成wangeditor富文本编辑器
- 实现图片上传功能
- 配置编辑器基本参数和样式

refactor: 优化代码调试信息和组件注册

- 移除多余的console.log调试语句
- 在组件声明文件中注册新组件
- 修复表格组件keyField字段配置

chore(env): 更新测试环境配置

- 修改测试环境服务基础URL配置
zhangtao 4 дней назад
Родитель
Сommit
e3f35cd119

+ 1 - 1
.env.test

@@ -2,7 +2,7 @@
 
 # VITE_SERVICE_BASE_URL=https://b8dbdde.r39.cpolar.top #王
 # VITE_SERVICE_BASE_URL=http://89561bkaq794.vicp.fun:53846 #张
-# VITE_SERVICE_BASE_URL=https://33cb5a99.r9.vip.cpolar.cn#田
+# 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

+ 2 - 0
src/components/zt/Form/hooks/useForm.ts

@@ -52,6 +52,8 @@ export function useForm(props?: Props): UseFormReturnType {
     },
     setFormProps: async (formProps: Partial<FormProps>) => {
       const form = await getForm();
+      console.log(formProps, 'formProps');
+
       await form.setFormProps(formProps);
     },
 

+ 33 - 25
src/components/zt/Table/z-table.vue

@@ -89,7 +89,33 @@ export default defineComponent({
       return data.value;
     }
     const getTableLoding = computed(() => loading.value);
+    const handleCommonTime = (form: Recordable) => {
+      const fieldMapToTime = getTableProps.value.fieldMapToTime;
 
+      if (!Array.isArray(fieldMapToTime)) {
+        return form;
+      }
+      fieldMapToTime.forEach(mapping => {
+        const [source, target] = mapping;
+        const formTimeKey = Array.isArray(source) ? source[0] : source;
+
+        let endTimeKey;
+        let startTimeKey;
+        if (Array.isArray(target)) {
+          if (typeof target[0] === 'string' && typeof target[1] === 'string') {
+            [startTimeKey, endTimeKey] = target;
+          } else if (Array.isArray(target[1])) {
+            [startTimeKey, endTimeKey] = target[1];
+          }
+        }
+
+        if (formTimeKey && startTimeKey && endTimeKey && form[formTimeKey]) {
+          [form[startTimeKey], form[endTimeKey]] = form[formTimeKey];
+          Reflect.deleteProperty(form, formTimeKey);
+        }
+      });
+      return form as Recordable;
+    };
     const TableMethod: TableMethods = {
       refresh: fetchSearchData,
       setSearchProps,
@@ -97,7 +123,7 @@ export default defineComponent({
       setTableConfig,
       getTableCheckedRowKeys,
       getTableData,
-      getSeachForm,
+      getSeachForm: () => handleCommonTime(getSeachForm()),
       setFieldsValue,
       getTableLoding
     };
@@ -126,31 +152,10 @@ export default defineComponent({
     async function fetchSearchData() {
       handleSearch();
     }
-    function handleSearch() {
-      const form = getSeachForm();
-      console.log(form);
 
-      if (getTableProps.value.fieldMapToTime && form[getTableProps.value.fieldMapToTime[0][0] as unknown as string]) {
-        const [startTimeKey, endTimeKey] = getTableProps.value.fieldMapToTime[0][1];
-        const formTimeKey = getTableProps.value.fieldMapToTime[0][0];
-        form[startTimeKey] = form[formTimeKey][0];
-        form[endTimeKey] = form[formTimeKey][1];
-        // delete form[formTimeKey];
-        Reflect.deleteProperty(form, formTimeKey);
-      }
-
-      if (
-        getTableProps.value.fieldMapToTime &&
-        getTableProps.value.fieldMapToTime[1] &&
-        form[getTableProps.value.fieldMapToTime[1][0] as unknown as string]
-      ) {
-        const [startTimeKey, endTimeKey] = getTableProps.value.fieldMapToTime[1][1];
-        const formTimeKey = getTableProps.value.fieldMapToTime[1][0];
-        form[startTimeKey] = form[formTimeKey][0];
-        form[endTimeKey] = form[formTimeKey][1];
-        // delete form[formTimeKey];
-        Reflect.deleteProperty(form, formTimeKey);
-      }
+    function handleSearch() {
+      const form = handleCommonTime(getSeachForm());
+      console.log(propsData.defaultParams, 'propsData.defaultParams');
 
       // 查询默认值,重置的时候不重置这个参数
       if (getTableProps.value.defaultParamsNotReset) {
@@ -209,6 +214,9 @@ export default defineComponent({
         </template>
       </TableHeaderOperation>
     </template>
+    <template #header>
+      <slot name="header" :loading="loading"></slot>
+    </template>
     <NDataTable
       v-bind="NTableProps"
       v-model:checked-row-keys="checkedRowKeys"

+ 94 - 0
src/components/zt/editor/index.vue

@@ -0,0 +1,94 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import WangEditor from 'wangeditor';
+import { fetchUpload } from '@/service/api/common';
+
+const editor = ref<WangEditor>();
+const domRef = ref<HTMLElement>();
+
+const context = defineModel<string>();
+const props = defineProps<{
+  height?: number;
+}>();
+const uploadConfig = {
+  // 服务器端点
+  server: '/smqjh-system/api/v1/files', // 请替换为你的图片上传接口
+  // 限制大小
+  maxSize: 5 * 1024 * 1024, // 5M
+  // 限制类型
+  allowedFileTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'],
+  // 超时时间
+  timeout: 30 * 1000
+};
+function renderWangEditor() {
+  editor.value = new WangEditor(domRef.value);
+  setEditorConfig();
+  configureImageUpload();
+  editor.value.create();
+  if (context.value) {
+    editor.value.txt.html(context.value);
+  }
+  editor.value.config.onchange = (newHtml: string) => {
+    context.value = newHtml;
+    console.log(newHtml, 'newHtml');
+  };
+}
+
+function setEditorConfig() {
+  console.log(editor.value, 'editor.valueeditor.value');
+
+  if (editor.value?.config?.zIndex) {
+    editor.value.config.zIndex = 1000;
+  }
+  if (editor.value) {
+    editor.value.config.height = props.height || 800;
+  }
+  // 启用粘贴文本保持格式
+  editor.value!.config.pasteFilterStyle = true;
+  editor.value!.config.pasteIgnoreImg = false;
+  editor.value!.config.pasteTextHandle = (content: string) => {
+    return content;
+  };
+}
+function configureImageUpload() {
+  if (!editor.value) return;
+
+  // 配置图片上传参数
+  const config = editor.value.config;
+
+  // 设置图片上传配置
+  config.uploadImgServer = uploadConfig.server;
+  config.uploadImgMaxSize = uploadConfig.maxSize;
+  config.uploadImgAccept = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
+  config.uploadImgTimeout = uploadConfig.timeout;
+  config.customUploadImg = async (files: File[], insertFn: (url: string) => void) => {
+    try {
+      const result = await fetchUpload(files[0]);
+      const imageUrl = result.data.url;
+      insertFn(imageUrl);
+    } catch (error) {
+      window.$message?.error(`上传失败${error}`);
+    }
+  };
+}
+onMounted(() => {
+  renderWangEditor();
+});
+</script>
+
+<template>
+  <div class="h-full w-full">
+    <div ref="domRef" class="w-full bg-white dark:bg-dark"></div>
+  </div>
+</template>
+
+<style scoped>
+:deep(.w-e-toolbar) {
+  background: inherit !important;
+  border-color: #999 !important;
+}
+:deep(.w-e-text-container) {
+  background: inherit;
+  border-color: #999 !important;
+}
+</style>

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

@@ -271,7 +271,11 @@ const local: App.I18n.Schema = {
     device_manage: '',
     'device_terminal-manage': '',
     operation: '',
-    'operation_accounting-strategy': ''
+    'operation_accounting-strategy': '',
+    'film-manage': '',
+    'film-manage_config': '',
+    'film-manage_film-list': '',
+    'film-manage_setprice': ''
   },
   page: {
     login: {

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

@@ -268,7 +268,11 @@ const local: App.I18n.Schema = {
     device_manage: '',
     'device_terminal-manage': '',
     operation: '',
-    'operation_accounting-strategy': ''
+    'operation_accounting-strategy': '',
+    'film-manage': '',
+    'film-manage_config': '',
+    'film-manage_film-list': '',
+    'film-manage_setprice': ''
   },
   page: {
     login: {

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

@@ -22,6 +22,9 @@ 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"),
+  "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_store-goods": () => import("@/views/goods-center/store-goods/index.vue"),
   "goods-center_type-admin": () => import("@/views/goods-center/type-admin/index.vue"),
   "government_government-list": () => import("@/views/government/government-list/index.vue"),

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

@@ -68,6 +68,44 @@ export const generatedRoutes: GeneratedRoute[] = [
       }
     ]
   },
+  {
+    name: 'film-manage',
+    path: '/film-manage',
+    component: 'layout.base',
+    meta: {
+      title: 'film-manage',
+      i18nKey: 'route.film-manage'
+    },
+    children: [
+      {
+        name: 'film-manage_config',
+        path: '/film-manage/config',
+        component: 'view.film-manage_config',
+        meta: {
+          title: 'film-manage_config',
+          i18nKey: 'route.film-manage_config'
+        }
+      },
+      {
+        name: 'film-manage_film-list',
+        path: '/film-manage/film-list',
+        component: 'view.film-manage_film-list',
+        meta: {
+          title: 'film-manage_film-list',
+          i18nKey: 'route.film-manage_film-list'
+        }
+      },
+      {
+        name: 'film-manage_setprice',
+        path: '/film-manage/setprice',
+        component: 'view.film-manage_setprice',
+        meta: {
+          title: 'film-manage_setprice',
+          i18nKey: 'route.film-manage_setprice'
+        }
+      }
+    ]
+  },
   {
     name: 'goods-center',
     path: '/goods-center',

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

@@ -184,6 +184,10 @@ const routeMap: RouteMap = {
   "device": "/device",
   "device_manage": "/device/manage",
   "device_terminal-manage": "/device/terminal-manage",
+  "film-manage": "/film-manage",
+  "film-manage_config": "/film-manage/config",
+  "film-manage_film-list": "/film-manage/film-list",
+  "film-manage_setprice": "/film-manage/setprice",
   "goods-center": "/goods-center",
   "goods-center_store-goods": "/goods-center/store-goods",
   "goods-center_type-admin": "/goods-center/type-admin",

+ 45 - 0
src/service/api/film-manage/config/index.ts

@@ -0,0 +1,45 @@
+import { request } from '@/service/request';
+
+/**
+ * 电影倒计时配置
+ * @param data
+ * @returns
+ */
+export function fetchCountdown(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/countdown',
+    method: 'POST',
+    data
+  });
+}
+/**
+ * 退改签协议配置
+ * @param data
+ * @returns
+ */
+export function fetchProtocol(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/protocol',
+    method: 'post',
+    data
+  });
+}
+
+/**
+ * 购票须知
+ * @param data
+ */
+export function fetchNotice(data: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/ticket/notice',
+    method: 'post',
+    data
+  });
+}
+
+export function fetchGetConfig() {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/config',
+    method: 'GET'
+  });
+}

+ 36 - 0
src/service/api/film-manage/film-list/index.ts

@@ -0,0 +1,36 @@
+import { request } from '@/service/request';
+
+/**
+ * 电影列表
+ */
+export function fetchMovieList(params: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/page',
+    method: 'POST',
+    data: params
+  });
+}
+
+/**
+ * 电影设置价格列表
+ * @param params
+ * @returns
+ */
+export function fetchSetMoivePrice(params: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/price',
+    method: 'get',
+    params
+  });
+}
+
+/** \
+ * 获取电影影院列表商户列表
+ */
+export function fetchMoivecinemaList(params: any) {
+  return request({
+    url: '/smqjh-pms/api/v1/movie/cinemaList',
+    method: 'GET',
+    params
+  });
+}

+ 13 - 0
src/service/api/operation/accounting-strategy/index.ts

@@ -38,3 +38,16 @@ export function fetchsaveBatchPolicyFee(params: any) {
     data: params
   });
 }
+
+/**
+ * 删除市民策略费用
+ * @param params
+ * @returns
+ */
+export function fetchDeletePolicyFee(params: any) {
+  return request({
+    url: '/smqjh-system/api/zsdd/deletePolicyFee',
+    method: 'POST',
+    data: params
+  });
+}

+ 259 - 0
src/typings/api.d.ts

@@ -878,6 +878,14 @@ declare namespace Api {
        * 买家手机号
        */
       userMobile?: string;
+      /**
+       * 电影票详情
+       */
+      yppDetail?: TicketOrderChannelDto;
+      /**
+       * 业务类型
+       */
+      businessType: string;
       [property: string]: any;
     }
     interface OrderItemElement {
@@ -1505,6 +1513,257 @@ declare namespace Api {
       withdrawApplyCount?: number;
       [property: string]: any;
     }
+    interface TicketOrderChannelDto {
+      /**
+       * 所属APPId
+       */
+      appId?: null | string;
+      /**
+       * 观众列表ID,用,分隔
+       */
+      audience?: null | string;
+      /**
+       * 头像地址
+       */
+      avatarUrl?: null | string;
+      /**
+       * 购买方式  1快速票、0特惠票
+       */
+      buyModel?: number;
+      /**
+       * 影院地址
+       */
+      cinemaAddress?: null | string;
+      /**
+       * 影院经度
+       */
+      cinemaLat?: number;
+      /**
+       * 影院纬度
+       */
+      cinemaLng?: number;
+      /**
+       * 影院名称
+       */
+      cinemaName?: null | string;
+      /**
+       * 城市名称
+       */
+      cityName?: null | string;
+      /**
+       * 优惠券ID
+       */
+      couponId?: null | string;
+      /**
+       * 创建时间
+       */
+      createTime?: Date;
+      /**
+       * 企业ID
+       */
+      entId?: null | string;
+      /**
+       * 企业参数
+       */
+      entPara?: null | string;
+      /**
+       * 当前订单佣金
+       */
+      epCommission?: number;
+      /**
+       * 订单结算价
+       */
+      epCostPrice?: number;
+      /**
+       * 上级代理佣金(无上级代理为0)
+       */
+      epParentCommission?: number;
+      /**
+       * 企业订单结算价已退金额
+       */
+      epRefundAmount?: number;
+      /**
+       * 归属企业用户id
+       */
+      epUserId?: string;
+      /**
+       * 过期时间
+       */
+      expireTime?: Date | null;
+      /**
+       * 附加数据
+       */
+      extData?: null | string;
+      /**
+       * 图片列表
+       */
+      files?: FileStoreDto[] | null;
+      /**
+       * 完成时间
+       */
+      finishTime?: Date | null;
+      /**
+       * 性别
+       */
+      gender?: null | string;
+      /**
+       * 影厅名称
+       */
+      hallName?: null | string;
+      id?: string;
+      /**
+       * 邀请企业佣金(无邀请企业为0)
+       */
+      inviteEpCommission?: number;
+      /**
+       * 出票时间(单位:秒)
+       */
+      makeTicketSeconds?: number;
+      /**
+       * 电影名称
+       */
+      movieName?: null | string;
+      /**
+       * 影票类型(英语 3D)
+       */
+      movieVersion?: null | string;
+      /**
+       * 昵称
+       */
+      nickName?: null | string;
+      /**
+       * 自定义订单号
+       */
+      orderCustomId?: null | string;
+      /**
+       * 外部平台支付凭证号
+       */
+      orderPayId?: null | string;
+      /**
+       * 外部平台退款凭证号
+       */
+      orderRefundId?: null | string;
+      orderState?: number;
+      /**
+       * 订单状态
+       */
+      orderStateName?: null | string;
+      /**
+       * 市场原价(单价)
+       */
+      originPrice?: number;
+      /**
+       * 支付时间
+       */
+      paidTime?: Date | null;
+      payAppId?: null | string;
+      payTranId?: null | string;
+      /**
+       * 电影海报地址
+       */
+      postImageUrl?: null | string;
+      seatList?: string[] | null;
+      /**
+       * 座位名称
+       */
+      seatNames?: null | string;
+      /**
+       * 开场时间
+       */
+      sessionBeginTime?: Date;
+      /**
+       * 结束时间
+       */
+      sessionEndTime?: Date | null;
+      /**
+       * 供应商ID
+       */
+      supplierId?: null | string;
+      /**
+       * 是否支持自动换座位
+       */
+      switchSeat?: boolean;
+      /**
+       * 取票码
+       */
+      ticketCode?: null | string;
+      /**
+       * 取票码列表
+       */
+      ticketCodeList?: string[] | null;
+      /**
+       * 取票码文字信息
+       */
+      ticketCodeText?: null | string;
+      /**
+       * 分销商总价(非分销模式为0)
+       */
+      totalDistributionPrice?: number;
+      /**
+       * 订单用户总价
+       */
+      totalUserPrice?: number;
+      /**
+       * 更新时间
+       */
+      updateTime?: Date | null;
+      /**
+       * 用户ID
+       */
+      userId?: string;
+      /**
+       * 微信form_id
+       */
+      wxFormId?: null | string;
+      /**
+       * 微信PrepayId
+       */
+      wxPrepayId?: null | string;
+    }
+
+    /**
+     * FileStoreDto
+     */
+    interface FileStoreDto {
+      /**
+       * 文件描述
+       */
+      fileDescription?: null | string;
+      /**
+       * 文件扩展名
+       */
+      fileExtension?: null | string;
+      /**
+       * 文件Hash
+       */
+      fileHash?: null | string;
+      /**
+       * 文件类型
+       */
+      fileMIME?: null | string;
+      /**
+       * 文件名称
+       */
+      fileName?: null | string;
+      /**
+       * 文件操作
+       */
+      fileOperation?: number;
+      /**
+       * 文件路径
+       */
+      filePath?: null | string;
+      id?: string;
+      /**
+       * 多图片索引
+       */
+      index?: number;
+      /**
+       * 最后操作时间
+       */
+      lastOperatedTime?: Date;
+      orderId?: string;
+    }
   }
   namespace Store {
     interface ShopDetail {

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

@@ -21,6 +21,7 @@ declare module 'vue' {
     DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
     DictSelect: typeof import('./../components/zt/dict-select/index.vue')['default']
     DictTag: typeof import('./../components/zt/dict-tag/index.vue')['default']
+    Editor: typeof import('./../components/zt/editor/index.vue')['default']
     ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
     FullScreen: typeof import('./../components/common/full-screen.vue')['default']
     GithubLink: typeof import('./../components/custom/github-link.vue')['default']
@@ -98,6 +99,7 @@ declare module 'vue' {
     NSwitch: typeof import('naive-ui')['NSwitch']
     NTab: typeof import('naive-ui')['NTab']
     NTable: typeof import('naive-ui')['NTable']
+    NTabPane: typeof import('naive-ui')['NTabPane']
     NTabs: typeof import('naive-ui')['NTabs']
     NTag: typeof import('naive-ui')['NTag']
     NTbody: typeof import('naive-ui')['NTbody']

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

@@ -38,6 +38,10 @@ declare module "@elegant-router/types" {
     "device": "/device";
     "device_manage": "/device/manage";
     "device_terminal-manage": "/device/terminal-manage";
+    "film-manage": "/film-manage";
+    "film-manage_config": "/film-manage/config";
+    "film-manage_film-list": "/film-manage/film-list";
+    "film-manage_setprice": "/film-manage/setprice";
     "goods-center": "/goods-center";
     "goods-center_store-goods": "/goods-center/store-goods";
     "goods-center_type-admin": "/goods-center/type-admin";
@@ -120,6 +124,7 @@ declare module "@elegant-router/types" {
     | "404"
     | "500"
     | "device"
+    | "film-manage"
     | "goods-center"
     | "government"
     | "home"
@@ -156,6 +161,9 @@ declare module "@elegant-router/types" {
     | "login"
     | "device_manage"
     | "device_terminal-manage"
+    | "film-manage_config"
+    | "film-manage_film-list"
+    | "film-manage_setprice"
     | "goods-center_store-goods"
     | "goods-center_type-admin"
     | "government_government-list"

+ 125 - 0
src/views/film-manage/config/index.vue

@@ -0,0 +1,125 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { NInputNumber } from 'naive-ui';
+import { fetchCountdown, fetchGetConfig, fetchNotice, fetchProtocol } from '@/service/api/film-manage/config';
+import { useForm } from '@/components/zt/Form/hooks/useForm';
+import type { FormSchema } from '@/components/zt/Form/types/form';
+type ActiveTab = 'countdown' | 'protocol' | 'notice';
+const activeTab = ref<ActiveTab>('countdown');
+const protocol = ref('');
+const countForm: FormSchema[] = [
+  {
+    label: '选票倒计时',
+    field: 'countdownVotes',
+    component: 'NInput',
+    render({ model, field }) {
+      return (
+        <NInputNumber
+          v-slots={{ suffix: () => '分钟' }}
+          value={model[field]}
+          onUpdate:value={val => (model[field] = val)}
+          min={0}
+          max={600000}
+        ></NInputNumber>
+      );
+    }
+  },
+  {
+    label: '付款倒计时',
+    field: 'countdownPay',
+    component: 'NInput',
+    render({ model, field }) {
+      return (
+        <NInputNumber
+          v-slots={{ suffix: () => '分钟' }}
+          value={model[field]}
+          onUpdate:value={val => (model[field] = val)}
+          max={10}
+          min={0}
+        ></NInputNumber>
+      );
+    }
+  }
+];
+const notice = ref();
+const submitLoding = ref(false);
+const [countdownForm, { getFieldsValue, setFieldsValue }] = useForm({
+  schemas: countForm,
+  labelWidth: 120,
+  layout: 'horizontal',
+  gridProps: {
+    cols: '1 xl:4 s:1 l:3',
+    itemResponsive: true
+  },
+  collapsedRows: 1,
+  showActionButtonGroup: false
+});
+async function handleChange(value: ActiveTab) {
+  activeTab.value = value;
+}
+async function handleSubmit() {
+  console.log(protocol.value, 'protocol');
+  submitLoding.value = true;
+
+  try {
+    if (activeTab.value == 'countdown') {
+      await fetchCountdown({ ...getFieldsValue() });
+    }
+    if (activeTab.value == 'protocol') {
+      await fetchProtocol({ protocol: protocol.value });
+    }
+    if (activeTab.value == 'notice') {
+      await fetchNotice({ notice: notice.value });
+    }
+    submitLoding.value = false;
+    window.$message?.success('保存成功');
+  } catch {
+    submitLoding.value = false;
+  }
+}
+
+async function getData() {
+  const { data, error } = await fetchGetConfig();
+  console.log(data);
+  if (!error) {
+    setFieldsValue({ ...data });
+    notice.value = data.notice;
+    protocol.value = data.protocol;
+  }
+}
+getData();
+</script>
+
+<template>
+  <div>
+    <NCard :bordered="false" class="h-full flex-1 card-wrapper bg-white sm:flex-1-hidden dark:bg-dark" size="small">
+      <NTabs v-model:value="activeTab" size="large" animated @update-value="handleChange">
+        <NTabPane name="countdown" tab="倒计时" display-directive="show">
+          <BasicForm @register-form="countdownForm"></BasicForm>
+        </NTabPane>
+        <NTabPane name="protocol" tab="退改签协议">
+          <div class="flex pt50px">
+            <div class="w120px flex-shrink-0 text-end">改签协议:</div>
+            <Editor v-model:model-value="protocol"></Editor>
+          </div>
+        </NTabPane>
+        <NTabPane name="notice" tab="购票须知">
+          <div class="flex">
+            <div class="w120px flex-shrink-0 text-end">购买须知:</div>
+            <NInput v-model:value="notice" type="textarea" placeholder="请输入购票须知" />
+          </div>
+        </NTabPane>
+      </NTabs>
+      <div class="mt40px w-full flex items-center justify-center">
+        <NButton :loading="submitLoding" type="primary" @click="handleSubmit">
+          <template #icon>
+            <SvgIcon icon="ant-design:save-outlined"></SvgIcon>
+          </template>
+          保存
+        </NButton>
+      </div>
+    </NCard>
+  </div>
+</template>
+
+<style lang="scss" scoped></style>

+ 211 - 0
src/views/film-manage/film-list/index.vue

@@ -0,0 +1,211 @@
+<script setup lang="tsx">
+import { ref, useTemplateRef } from 'vue';
+import { NButton } from 'naive-ui';
+import { router } from '@/router';
+import { fetchGetChannelList, fetchImportGoods } from '@/service/api/goods-center/store-goods';
+import { fetchMoivecinemaList, fetchMovieList } from '@/service/api/film-manage/film-list';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+const importTemplateRef = useTemplateRef('importTemplateRef');
+
+const options = ref<Api.goods.Channel[]>([]);
+const statusList = ['上架', '可售', '下架', '即将上映'];
+const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    type: 'selection',
+    align: 'center',
+    width: 48,
+    fixed: 'left'
+  },
+  {
+    key: 'supId',
+    title: '商品ID',
+    align: 'left',
+    width: 200,
+    render: (row: any) => {
+      return (
+        <div class={'flex items-center'}>
+          <div>统一ID:</div>
+          <div>{row.movieId}</div>
+        </div>
+      );
+    }
+  },
+  {
+    key: 'pic',
+    title: '商品图片',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return <n-image src={row.posterUrl} width={60} height={60}></n-image>;
+    }
+  },
+
+  {
+    key: 'movieName',
+    title: '商品名称',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'businessType',
+    title: '业务类型',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'cinemaName',
+    title: '商户',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  },
+  {
+    key: 'channelVOS',
+    title: '价格',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return (
+        <div>
+          {row.moviePriceVo.map((it: Api.government.ChannelVO) => {
+            return (
+              <div>
+                {it.channl}:¥{it.price}
+              </div>
+            );
+          })}
+        </div>
+      );
+    }
+  },
+  {
+    key: 'status',
+    title: '状态',
+    align: 'center',
+    width: 120,
+    render: (row: any) => {
+      return (
+        <div class="flex items-center justify-center">
+          <n-badge color={row.status == 0 ? 'green' : 'red'} value={row.status} dot />
+          <span class="ml-2">{statusList[row.status]}</span>
+        </div>
+      );
+    }
+  },
+  {
+    key: 'updateTime',
+    title: '更新时间',
+    align: 'center',
+    width: 120,
+    ellipsis: {
+      tooltip: true
+    }
+  }
+];
+const [registerTable] = useTable({
+  searchFormConfig: {
+    schemas: [
+      {
+        label: '商户',
+        component: 'ApiSelect',
+        field: 'cinemaName',
+        componentProps: {
+          api: fetchMoivecinemaList,
+          labelFeild: 'name',
+          valueFeild: 'name'
+        }
+      },
+      {
+        label: '关键词',
+        component: 'NInput',
+        field: 'keywords'
+      },
+      {
+        label: '状态',
+        field: 'status',
+        component: 'NSelect',
+        componentProps: {
+          options: [
+            {
+              label: '上架',
+              value: 0
+            },
+            {
+              label: '可售',
+              value: 1
+            },
+            {
+              label: '下架',
+              value: 2
+            },
+            {
+              label: '即将上映',
+              value: 3
+            }
+          ]
+        }
+      }
+    ],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'cinemaId',
+    title: '电影列表',
+    showAddButton: false,
+    scrollX: 1800,
+    fieldMapToTime: [
+      ['price', ['minPrice', 'maxPrice']],
+      ['createTime', ['startTime', 'endTime']]
+    ]
+  }
+});
+
+async function handleSubmitImport(file: File) {
+  const { error } = await fetchImportGoods({ file });
+  if (!error) {
+    importTemplateRef.value?.closeModal();
+  }
+  importTemplateRef.value?.setSubLoading(false);
+}
+
+function handleModalPrice(row: Api.goods.ShopSku) {
+  router.push({ path: '/film-manage/setprice', query: { movieId: row.movieId, cinemaId: row.cinemaId } });
+}
+async function getData() {
+  const { data, error } = await fetchGetChannelList();
+  if (!error) {
+    options.value = data;
+  }
+}
+getData();
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchMovieList" @register="registerTable">
+      <template #op="{ row }">
+        <NButton size="small" ghost type="primary" @click="handleModalPrice(row)">设置渠道及价格</NButton>
+      </template>
+    </ZTable>
+    <ZImportTemplate
+      ref="importTemplateRef"
+      url="/smqjh-pms/api/v1/channelProd/template/download"
+      template-text="商品渠道及价格导入模版.xlsx"
+      modal-text="导入商品销售渠道及价格"
+      @submit="handleSubmitImport"
+    ></ZImportTemplate>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 67 - 0
src/views/film-manage/setprice/index.vue

@@ -0,0 +1,67 @@
+<script setup lang="tsx">
+import { ref } from 'vue';
+import { useRoute } from 'vue-router';
+import { fetchSetMoivePrice } from '@/service/api/film-manage/film-list';
+const route = useRoute();
+const dataList = ref([]);
+async function getData() {
+  const { data } = await fetchSetMoivePrice(route.query);
+  dataList.value = data;
+}
+const columns: NaiveUI.TableColumn<Api.goods.ShopSku>[] = [
+  {
+    title: '规格信息',
+    key: 'specInfo',
+    width: 200,
+    render: row => {
+      return (
+        <div>
+          <div class={'text-gray'}>规格ID: {row.areaId} </div>
+          <div> {row.movieName} </div>
+        </div>
+      );
+    }
+  },
+  {
+    title: '企业',
+    key: 'enterpriseName',
+    width: 300,
+    align: 'center',
+    render(row) {
+      return row.channel.map((it: any) => {
+        return <div>{it}</div>;
+      });
+    }
+  },
+  {
+    title: '当前单价(元)',
+    key: 'price',
+    align: 'center',
+    width: 200,
+    render(row) {
+      return row.channel.map((_it: any) => {
+        return <div>{row.originPrice}</div>;
+      });
+    }
+  },
+  {
+    title: '改后单价(元)',
+    key: 'newprice',
+    width: 200
+  }
+];
+getData();
+</script>
+
+<template>
+  <div>
+    <NCard :bordered="false" class="h-full flex-1 card-wrapper bg-white sm:flex-1-hidden dark:bg-dark" size="small">
+      <h1 class="text-24px font-semibold">设置价格</h1>
+      <div class="mt40px">
+        <NDataTable :columns="columns" :data="dataList" :bordered="false" />
+      </div>
+    </NCard>
+  </div>
+</template>
+
+<style scoped></style>

+ 1 - 1
src/views/goods-center/store-goods/index.vue

@@ -323,7 +323,7 @@ const [registerTable, { getTableCheckedRowKeys, refresh, getTableData, getSeachF
     isFull: false
   },
   tableConfig: {
-    keyField: 'id',
+    keyField: 'skuId',
     title: '商品列表',
     showAddButton: false,
     scrollX: 1800,

+ 26 - 7
src/views/operation/accounting-strategy/index.vue

@@ -2,6 +2,7 @@
 import { nextTick, ref } from 'vue';
 import { NButton, NDataTable, NInputNumber, NTag } from 'naive-ui';
 import {
+  fetchDeletePolicyFee,
   fetchGetgetStationInfoPageByEquipment,
   fetchgetPolicyFee,
   fetchsaveBatchPolicyFee
@@ -173,7 +174,13 @@ const ModelColumns: NaiveUI.TableColumn<Api.device.ServiceRateConfig>[] = [
       return row.channelCDVOS.map(it => {
         return (
           <div class={'mt10px'}>
-            <NInputNumber step={0.01} value={it.opFee} onUpdate:value={val => (it.opFee = val)} min={0}></NInputNumber>
+            <NInputNumber
+              precision={2}
+              step={0.01}
+              value={it.opFee}
+              onUpdate:value={val => (it.opFee = val)}
+              min={0}
+            ></NInputNumber>
           </div>
         );
       });
@@ -250,7 +257,7 @@ const [registerTable] = useTable({
 });
 const [registerModal, { openModal, setModalLoading, setSubLoading, closeModal }] = useModal({
   title: '',
-  width: 1200,
+  width: 1400,
   height: 800
 });
 const [registerForm, { getFieldsValue, setFieldsValue, validate }] = useForm({
@@ -338,7 +345,7 @@ const [registerForm, { getFieldsValue, setFieldsValue, validate }] = useForm({
       }
     }
   ],
-  labelWidth: 180,
+  labelWidth: 100,
   layout: 'horizontal',
   gridProps: {
     cols: '1',
@@ -347,12 +354,13 @@ const [registerForm, { getFieldsValue, setFieldsValue, validate }] = useForm({
   collapsedRows: 1,
   showActionButtonGroup: false
 });
-
+const initFirmIds = ref<number[]>([]);
 async function getModelTableList() {
   const model = await getFieldsValue();
   const { data, error } = await fetchgetPolicyFee({ ...model });
   if (!error) {
     const channelIds = data[0].channelCDVOS ? data[0].channelCDVOS.map(it => it.firmId) : [];
+    initFirmIds.value = channelIds;
     await setFieldsValue({
       channelPartyId: channelIds,
       policyFees: data.map(it => {
@@ -373,6 +381,7 @@ async function getModelTableList() {
     });
   }
 }
+
 async function handleEidt(row: Api.device.ThirdPartyStationInfoPageVO) {
   setModalLoading(true);
   openModal();
@@ -391,7 +400,7 @@ function formatTime(timeStr: string): string {
 
   return timeStr.replace(/(\d{2})(\d{2})(\d{2})/, '$1:$2:$3');
 }
-function handleSetTabData(values: number[]) {
+async function handleSetTabData(values: number[]) {
   const modelData = getFieldsValue();
   const tableDATA = modelData.policyFees || [];
   const newTableData = tableDATA.map((it: Api.device.ServiceRateConfig) => {
@@ -414,8 +423,6 @@ function handleSetTabData(values: number[]) {
       })
     };
   });
-  console.log(newTableData, 'newTableData');
-
   setFieldsValue({ policyFees: newTableData });
 }
 async function handleSubmit() {
@@ -435,8 +442,20 @@ async function handleSubmit() {
       })
     };
   });
+  const nowFirmIds = modelData.channelPartyId;
+  const delids = initFirmIds.value.filter(it => !nowFirmIds.includes(it));
+  console.log(delids, '删除ids============');
   try {
     await fetchsaveBatchPolicyFee(tableData);
+    if (delids.length) {
+      const updatePolicyFeeFroms = delids.map(it => {
+        return {
+          firmId: it,
+          stationInfoId: modelData.stationId
+        };
+      });
+      await fetchDeletePolicyFee(updatePolicyFeeFroms);
+    }
     closeModal();
   } catch {
     setSubLoading(false);

+ 131 - 247
src/views/order-manage/normal-order/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="tsx">
-import { onMounted, reactive, ref, unref, useTemplateRef, watch } from 'vue';
+import { nextTick, ref, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import { NTag, useDialog } from 'naive-ui';
 import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
@@ -11,157 +11,83 @@ import {
   fetchGetDeliveryStatusNum
 } from '@/service/api/order-manage/normal-order';
 // import { fetchGetLoginUserList } from '@/service/api/common';
-import { useAppStore } from '@/store/modules/app';
-import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
 import { useAuth } from '@/hooks/business/auth';
 // import { copyTextToClipboard } from '@/utils/zt';
 import { commonExport } from '@/utils/common';
-import { $t } from '@/locales';
-import { useForm } from '@/components/zt/Form/hooks/useForm';
 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 DeliveryModal from './component/delivery-modal.vue';
-import NormalMoadl from './component/normal-modal.vue';
 const router = useRouter();
-const appStore = useAppStore();
-const checkedRowKeys = ref([]);
 const activeTab = ref('all');
 const statusList = ref<{ label: string; value: string; num?: number }[]>([]);
-const orderMoadl = useTemplateRef('orderMoadl');
 // const ShipmentModal = useTemplateRef('Shipment');
 const channelIdList = ref([]);
-const searchForm = ref();
-const searchParams = reactive({
-  pageNum: 1,
-  pageSize: 10
-});
-const [registerSearchForm, { getFieldsValue, setFieldsValue }] = useForm({
-  schemas: [
-    // {
-    //   field: 'channelIdList',
-    //   label: '所属企业',
-    //   component: 'ApiSelect',
-    //   componentProps: {
-    //     api: () => fetchGetLoginUserList(),
-    //     labelFeild: 'channelName',
-    //     valueFeild: 'id',
-    //     multiple: true,
-    //     onUpdateValue: () => {
-    //       nextTick(() => {
-    //         handleSearch();
-    //       });
-    //     },
-    //     getOptions: async (options: any) => {
-    //       await setFieldsValue({ channelIdList: [options[0].id] });
-    //       handleSearch();
-    //     }
-    //   }
-    // },
-    ...SearchForm
-  ],
-  showAdvancedButton: false,
-  labelWidth: 120,
-  layout: 'horizontal',
-  size: 'small',
-  gridProps: {
-    cols: '1 xl:4 s:1 l:3',
-    itemResponsive: true
-  },
-  collapsedRows: 1
-});
-const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedTable({
-  api: () => fetchGetDeliveryOrderList({ ...searchParams, orderStatus: activeTab.value, ...unref(searchForm) }),
-  transform: response => defaultTransform(response),
-  immediate: false,
-  paginationProps: {
-    pageSizes: import.meta.env.VITE_PAGE_SIZE.split(',').map(Number)
+const columns: NaiveUI.TableColumn<Api.delivery.deliveryOrder>[] = [
+  {
+    key: 'orderNumber',
+    title: '订单编号',
+    align: 'center',
+    width: 220
   },
-  onPaginationParamsChange: params => {
-    searchParams.pageNum = Number(params.page);
-    searchParams.pageSize = Number(params.pageSize);
+  {
+    key: 'consigneeAddress',
+    title: '业务类型',
+    align: 'center',
+    width: 120,
+    render: row => {
+      return <NTag class={'mt7'}>{row.businessType}</NTag>;
+    }
   },
-  columns: () => [
-    {
-      key: 'orderNumber',
-      title: '订单编号',
-      align: 'center',
-      width: 220
-    },
-    {
-      key: 'consigneeAddress',
-      title: '业务类型',
-      align: 'center',
-      width: 120,
-      render: row => {
-        return <NTag class={'mt7'}>{row.businessType}</NTag>;
-      }
-    },
-    {
-      key: 'info',
-      title: '客户信息',
-      align: 'center',
-      width: 220,
-      render: row => {
-        return (
-          <div class={'mt7'}>
-            <div>
-              {row.consigneeName}
-              {row.consigneeMobile}
-            </div>
-            <div>{row.consigneeAddress}</div>
-          </div>
-        );
-      }
-    },
-    {
-      key: 'status',
-      title: '订单状态',
-      align: 'center',
-      width: 120,
-      render: row => {
-        const statusKey = row.hbOrderStatus as keyof typeof orderStatus;
-        const statusText = orderStatus[statusKey] || '未知状态';
-        return <NTag class={'mt7'}>{statusText}</NTag>;
-      }
-    },
-    {
-      key: 'createTime',
-      title: '下单时间',
-      align: 'center',
-      width: 180,
-      render: row => {
-        return <div>{row.createTime?.replace('T', '  ')}</div>;
-      }
-    },
-    {
-      key: 'operate',
-      title: $t('common.operate'),
-      align: 'center',
-      width: 150,
-      fixed: 'right',
-      render: row => {
-        return (
-          <div class={'mt7'}>
-            <n-button size="small" type="primary" ghost onClick={() => handleOrderDetail(row)}>
-              订单详情
-            </n-button>
+  {
+    key: 'info',
+    title: '客户信息',
+    align: 'center',
+    width: 220,
+    render: row => {
+      return (
+        <div class={'mt7'}>
+          <div>
+            {row.consigneeName}
+            {row.consigneeMobile}
           </div>
-        );
-      }
+          <div>{row.consigneeAddress}</div>
+        </div>
+      );
     }
-  ]
-});
+  },
+  {
+    key: 'status',
+    title: '订单状态',
+    align: 'center',
+    width: 120,
+    render: row => {
+      const statusKey = row.hbOrderStatus as keyof typeof orderStatus;
+      const statusText = orderStatus[statusKey] || '未知状态';
+      return <NTag class={'mt7'}>{statusText}</NTag>;
+    }
+  },
+  {
+    key: 'createTime',
+    title: '下单时间',
+    align: 'center',
+    width: 180,
+    render: row => {
+      return <div>{row.createTime?.replace('T', '  ')}</div>;
+    }
+  }
+];
 
-const [registerTable, { refresh, setTableLoading }] = useTable({
+const [registerTable, { refresh, setTableLoading, getSeachForm, getTableData }] = useTable({
+  searchFormConfig: {
+    schemas: [...SearchForm]
+  },
   tableConfig: {
     keyField: 'id',
     title: '',
     showAddButton: false,
-    showTableHeaderAction: false,
-    showSearch: false,
-    minHeight: 400
+    showTableHeaderAction: true,
+    fieldMapToTime: [['createTime', ['startTime', 'endTime']]]
   }
 });
 
@@ -285,12 +211,12 @@ function handleOrderDetail(row: Api.delivery.deliveryOrder) {
   // orderMoadl.value?.open(String(row.orderNumber));
 }
 async function getNums() {
-  const form = getFieldsValue();
+  const form = getSeachForm();
   const params = {
     ...form,
     channelIdList: channelIdList.value,
-    startTime: form.createTime ? form.createTime[0] : null,
-    endTime: form.createTime ? form.createTime[1] : null,
+    startTime: form?.createTime && form.createTime[0],
+    endTime: form?.createTime && form.createTime[1],
     createTime: null
   };
   const { data: keyData } = await fetchGetDeliveryStatusNum(params);
@@ -331,37 +257,8 @@ async function getNums() {
   statusList.value = updatedOrderStatusList;
 }
 
-watch(
-  () => [activeTab.value],
-  () => {
-    searchParams.pageNum = 1;
-    getData();
-  }
-);
-function handleSearch() {
-  const form = getFieldsValue();
-  if (form.createTime) {
-    form.startTime = form.createTime[0];
-    form.endTime = form.createTime[1];
-    delete form.createTime;
-  }
-  channelIdList.value = form.channelIdList;
-  searchForm.value = form;
-  getData();
-  getNums();
-}
-onMounted(() => {
-  getData();
-  getNums();
-});
+getNums();
 
-function handleReset() {
-  searchForm.value = getFieldsValue();
-  searchForm.value.channelIdList = channelIdList.value;
-  setFieldsValue({ channelIdList: channelIdList.value });
-  getData();
-  getNums();
-}
 // async function handleCopy(row: Api.delivery.deliveryOrder, key: string) {
 //   if (!row[key]) {
 //     window.$message?.error('订单编号不存在');
@@ -369,25 +266,16 @@ function handleReset() {
 //   }
 //   await copyTextToClipboard(row[key]);
 // }
-function handleRefsh() {
-  getData();
-  getNums();
-}
+
 async function handleExport() {
-  loading.value = true;
+  setTableLoading(true);
   try {
     // await commonExport(
     //   '/platform/order/export',
     //   { ...getFieldsValue(), orderStatus: activeTab.value },
     //   '正常订单列表.xlsx'
     // );
-    const newParams = getFieldsValue();
-    if (newParams.createTime) {
-      newParams.startTime = newParams.createTime[0];
-      newParams.endTime = newParams.createTime[1];
-      delete newParams.createTime;
-    }
-    await fetchExportOrderList({ ...newParams, orderStatus: activeTab.value });
+    await fetchExportOrderList({ ...getSeachForm(), orderStatus: activeTab.value });
     dialog.success({
       title: '提示',
       content: () => {
@@ -406,92 +294,88 @@ async function handleExport() {
       onNegativeClick: () => {}
     });
   } finally {
-    loading.value = false;
+    setTableLoading(false);
   }
 }
 
 async function handleExportLog() {
-  loading.value = true;
+  setTableLoading(true);
   try {
     openModal();
   } finally {
-    loading.value = false;
+    setTableLoading(false);
   }
 }
+watch(
+  () => [activeTab.value],
+  () => {
+    nextTick(() => {
+      refresh();
+    });
+  }
+);
 </script>
 
 <template>
-  <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
-    <NCard :bordered="false" size="small">
-      <NCollapse display-directive="show" default-expanded-names="search">
-        <NCollapseItem title="搜索" name="search">
-          <BasicForm @register-form="registerSearchForm" @submit="handleSearch" @reset="handleReset" />
-        </NCollapseItem>
-      </NCollapse>
-    </NCard>
-    <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
+  <LayoutTable>
+    <ZTable
+      :columns="columns"
+      :api="fetchGetDeliveryOrderList"
+      :default-params="{ orderStatus: activeTab }"
+      @register="registerTable"
+    >
+      <template #op="{ row }">
+        <NButton size="small" type="primary" ghost @click="handleOrderDetail(row)">订单详情</NButton>
+      </template>
       <template #header>
-        <div class="mr3">订单列表</div>
-        <NScrollbar x-scrollable>
-          <div class="flex items-center">
-            <div class="max-w-800px">
-              <NTabs v-model:value="activeTab" type="line" animated display-directive="show">
-                <NTab
-                  v-for="item in statusList"
-                  :key="item.value"
-                  :name="item.value"
-                  :tab="`${item.label}(${item.num})`"
-                ></NTab>
-              </NTabs>
+        <div class="flex items-center">
+          <NScrollbar x-scrollable>
+            <div class="flex items-center">
+              <div class="max-w-800px">
+                <NTabs v-model:value="activeTab" type="line" animated display-directive="show">
+                  <NTab
+                    v-for="item in statusList"
+                    :key="item.value"
+                    :name="item.value"
+                    :tab="`${item.label}(${item.num})`"
+                  ></NTab>
+                </NTabs>
+              </div>
             </div>
-          </div>
-        </NScrollbar>
+          </NScrollbar>
+        </div>
       </template>
-      <template #header-extra>
-        <NButton
-          v-if="useAuth().hasAuth('order:user:export')"
-          size="small"
-          type="primary"
-          class="ml20px mt30px"
-          ghost
-          :loading="loading"
-          :disabled="data.length == 0"
-          @click="handleExport"
-        >
-          <template #icon>
-            <SvgIcon icon="mingcute:file-export-line"></SvgIcon>
-          </template>
-          导出
-        </NButton>
-        <NButton
-          v-if="useAuth().hasAuth('order:user:export')"
-          size="small"
-          type="primary"
-          class="ml20px mt30px"
-          ghost
-          :loading="loading"
-          @click="handleExportLog"
-        >
-          导出记录
-        </NButton>
+      <template #prefix="{ loading }">
+        <div class="flex items-center">
+          <NButton
+            v-if="useAuth().hasAuth('order:user:export')"
+            size="small"
+            type="primary"
+            class="ml20px mt30px"
+            ghost
+            :loading="loading"
+            :disabled="getTableData().length == 0"
+            @click="handleExport"
+          >
+            <template #icon>
+              <SvgIcon icon="mingcute:file-export-line"></SvgIcon>
+            </template>
+            导出
+          </NButton>
+          <NButton
+            v-if="useAuth().hasAuth('order:user:export')"
+            size="small"
+            type="primary"
+            class="ml20px mt30px"
+            ghost
+            :loading="loading"
+            @click="handleExportLog"
+          >
+            导出记录
+          </NButton>
+        </div>
       </template>
-
-      <NDataTable
-        v-model:checked-row-keys="checkedRowKeys"
-        :columns="columns"
-        :data="data"
-        size="small"
-        :flex-height="!appStore.isMobile"
-        :scroll-x="1800"
-        :loading="loading"
-        :row-key="row => row.orderId"
-        remote
-        class="sm:h-full"
-        :pagination="mobilePagination"
-      />
-      <NormalMoadl ref="orderMoadl" @finish="handleRefsh"></NormalMoadl>
-      <DeliveryModal ref="Shipment" @finish="handleRefsh"></DeliveryModal>
-    </NCard>
+    </ZTable>
     <BasicModal @register="registerModalPrice" @after-leave="refresh">
       <LayoutTable>
         <ZTable
@@ -503,7 +387,7 @@ async function handleExportLog() {
         ></ZTable>
       </LayoutTable>
     </BasicModal>
-  </div>
+  </LayoutTable>
 </template>
 
 <style scoped></style>

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

@@ -183,7 +183,36 @@ export const orderStatus = {
   [orderStatusEnum.ORDER_ARRIVE]: '订单已送达',
   [orderStatusEnum.ORDER_COMPLETE]: '订单已完成'
 };
-
+export enum yppStatusEnum {
+  /**
+   *
+   *  待支付
+   */
+  WAIT_PAY = 0,
+  /**
+   * 已支付
+   */
+  WAIT_DELIVERY = 1,
+  /**
+   * 出票成功
+   */
+  SUCCESS = 4,
+  /**
+   * 出票失败(退款)
+   */
+  REFUND_FAIL = 7,
+  /**
+   * 超时未支付(取消)
+   */
+  TIMEOUT_CANCEL = 8
+}
+export const yppStatus = {
+  [yppStatusEnum.WAIT_PAY]: '待支付',
+  [yppStatusEnum.WAIT_DELIVERY]: '已支付',
+  [yppStatusEnum.SUCCESS]: '出票成功',
+  [yppStatusEnum.REFUND_FAIL]: '出票失败(退款)',
+  [yppStatusEnum.TIMEOUT_CANCEL]: '超时未支付(取消)'
+};
 /**
  // 1:申请退款 2:退款成功 3:部分退款成功 4:退款失败
  *

+ 129 - 59
src/views/order-manage/order-detail/index.vue

@@ -6,15 +6,11 @@ import dayjs from 'dayjs';
 import { fetchGetNomalOrderInfo } from '@/service/api/order-manage/normal-order';
 // import { useAppStore } from '@/store/modules/app';
 // import { copyTextToClipboard } from '@/utils/zt';
-import { orderColumns, orderStatus, orderStatusEnum } from '../normal-order/normal-order';
-import DeliveryModal from '../normal-order/component/delivery-modal.vue';
+import { orderColumns, orderStatus, orderStatusEnum, yppStatus, yppStatusEnum } from '../normal-order/normal-order';
 const orderInfo = ref<Api.delivery.deliveryOrder>();
 // const appStore = useAppStore();
 const TimeDown = ref<number>(0);
-// const ShipmentRef = useTemplateRef('Shipment');
-const emit = defineEmits<{
-  (e: 'finish'): void;
-}>();
+
 const route = useRoute();
 async function open(orderNumber: string) {
   const { data, error } = await fetchGetNomalOrderInfo(orderNumber);
@@ -45,7 +41,7 @@ defineExpose({ open });
 //       return 1;
 //     case orderStatusEnum.ORDER_ACCEPT:
 //       return 2;
-//     case orderStatusEnum.ORDER_WAIT_DELIVERY:
+//     case orderStatusEnum.ORDER_WAIT_DELIVE RY:
 //       return 2;
 //     case orderStatusEnum.WAIT_DELIVERY:
 //       return 2;
@@ -71,21 +67,31 @@ defineExpose({ open });
       <div class="mr-20px w-300px">
         <div class="mb-10px text-16px font-semibold">
           统一状态:
-          {{ orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus] }}
+          <template v-if="orderInfo.businessType != 'DYY'">
+            {{ orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus] }}
+          </template>
+          <template v-else>
+            {{ yppStatus[orderInfo.yppDetail?.orderState as keyof typeof yppStatus] }}
+          </template>
         </div>
         <NCard size="small" title="订单概览" :bordered="false">
           <div>订单编号:{{ orderInfo.orderNumber }}</div>
           <div>业务类型:{{ orderInfo.businessType }}</div>
           <div>
             订单状态:
-            {{
-              orderInfo.hbOrderStatus == 20 ||
-              orderInfo.hbOrderStatus == 30 ||
-              orderInfo.hbOrderStatus == 40 ||
-              orderInfo.hbOrderStatus == 70
-                ? '进行中'
-                : orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus]
-            }}
+            <template v-if="orderInfo.businessType != 'DYY'">
+              {{
+                orderInfo.hbOrderStatus == 20 ||
+                orderInfo.hbOrderStatus == 30 ||
+                orderInfo.hbOrderStatus == 40 ||
+                orderInfo.hbOrderStatus == 70
+                  ? '进行中'
+                  : orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus]
+              }}
+            </template>
+            <template v-else>
+              {{ yppStatus[orderInfo.yppDetail?.orderState as keyof typeof yppStatus] }}
+            </template>
           </div>
           <div>订单金额:{{ orderInfo.actualTotal }}</div>
 
@@ -123,57 +129,121 @@ defineExpose({ open });
         <div class="flex">
           <div class="mb-10px text-16px font-semibold">
             业务状态:
-            {{ orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus] }}
+            <template v-if="orderInfo.businessType == 'XSB'">
+              {{ orderStatus[orderInfo.hbOrderStatus as keyof typeof orderStatus] }}
+            </template>
+            <template v-if="orderInfo.businessType == 'DYY'">
+              {{ yppStatus[orderInfo.yppDetail?.orderState as keyof typeof yppStatus] }}
+            </template>
           </div>
           <div class="mb-10px ml-20px text-16px font-semibold">第三方订单编号:{{ orderInfo.orderNumber }}</div>
         </div>
         <NCard size="small" title="业务信息" :bordered="false">
-          <div class="pb-20px font-semibold">01 商家信息</div>
-          <div>门店名称:{{ orderInfo.shopName || '暂无' }}</div>
-          <div>门店地址:{{ orderInfo.shopAddress || '暂无' }}</div>
-          <div>联系电话:{{ orderInfo.tel || '暂无' }}</div>
-          <div class="py-20px font-semibold">02 商品&费用</div>
-          <NDataTable :columns="orderColumns" :data="orderInfo.orderItemList" :bordered="false" />
+          <template v-if="orderInfo.businessType == 'XSB'">
+            <div class="pb-20px font-semibold">01 商家信息</div>
+            <div>门店名称:{{ orderInfo.shopName || '暂无' }}</div>
+            <div>门店地址:{{ orderInfo.shopAddress || '暂无' }}</div>
+            <div>联系电话:{{ orderInfo.tel || '暂无' }}</div>
+            <div class="py-20px font-semibold">02 商品&费用</div>
+            <NDataTable :columns="orderColumns" :data="orderInfo.orderItemList" :bordered="false" />
 
-          <NTable :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>
+            <NTable :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>
+          </template>
+          <template v-if="orderInfo.businessType == 'DYY'">
+            <div class="pb-20px font-semibold">01 影片与场次信息</div>
+            <div>影片名称:{{ orderInfo.yppDetail?.movieName || '暂无' }}</div>
+            <div class="flex items-center justify-between">
+              <div>影院名称:{{ orderInfo.yppDetail?.cinemaName || '--' }}</div>
+              <div>影厅:{{ orderInfo.yppDetail?.hallName || '--' }}</div>
+            </div>
+            <div class="flex items-center justify-between">
+              <div>放映时间:{{ orderInfo.yppDetail?.sessionBeginTime || '--' }}</div>
+              <div>结束时间:{{ orderInfo.yppDetail?.sessionEndTime || '--' }}</div>
+            </div>
+            <div class="flex items-center justify-between">
+              <div>版本类型:{{ orderInfo.yppDetail?.movieVersion || '--' }}</div>
+              <div>语言:{{ orderInfo.yppDetail?.movieVersion || '--' }}</div>
+            </div>
+            <div class="flex items-center justify-between">
+              <div>座位信息: {{ orderInfo.yppDetail?.seatNames || '--' }}</div>
+              <div>快速票:{{ orderInfo.yppDetail?.buyModel ? '是' : '否' }}</div>
+            </div>
+            <div class="pb-20px font-semibold">02 价格与结算信息</div>
+            <div v-if="yppStatusEnum.WAIT_PAY == orderInfo.yppDetail?.orderState" class="font-semibold">
+              【客户需支付信息】
+            </div>
+            <div
+              :class="[
+                yppStatusEnum.WAIT_PAY != orderInfo.yppDetail?.orderState ? 'flex items-center justify-between' : ''
+              ]"
+            >
+              <div v-if="yppStatusEnum.WAIT_PAY != orderInfo.yppDetail?.orderState">
+                <div class="font-semibold">【客户已支付信息】</div>
+                <div>市场原价(单价):¥{{ orderInfo.yppDetail?.originPrice || '--' }}</div>
+                <div>原票价总价:¥{{ orderInfo.yppDetail?.totalUserPrice || '--' }}</div>
+                <div>积分抵扣: ¥{{ (Number(orderInfo.offsetPoints) / 100).toFixed(2) || 0 }}</div>
+                <div>客户实际支付:¥{{ orderInfo.actualTotal }}</div>
+              </div>
+              <template v-if="yppStatusEnum.WAIT_PAY != orderInfo.yppDetail?.orderState">
+                <div>
+                  <div class="font-semibold">【预存款结算信息】</div>
+                  <div>企业扣款价:¥--</div>
+                  <div>结算价已退金额:¥--</div>
+                  <div>订单成本价:¥--</div>
+                </div>
+              </template>
+            </div>
+            <template
+              v-if="
+                [yppStatusEnum.SUCCESS, yppStatusEnum.REFUND_FAIL].includes(orderInfo.yppDetail?.orderState as number)
+              "
+            >
+              <div class="pb-20px font-semibold">03 取票与退款信息</div>
+              <div class="flex flex-wrap items-center">
+                <div v-for="item in orderInfo.yppDetail?.seatList" :key="item" class="w30%">
+                  <div>座位:{{ item }}</div>
+                  <div>取票码:640085 | 661789</div>
+                </div>
+              </div>
+            </template>
+          </template>
         </NCard>
       </div>
     </div>
-    <DeliveryModal ref="Shipment" @finish="emit('finish')"></DeliveryModal>
   </div>
 </template>