瀏覽代碼

Merge branch 'release/zfb-3.4-202301110900'

gemercheung 2 年之前
父節點
當前提交
1cfaf63a2f

+ 10 - 0
src/api/rightsEnterprises/list.ts

@@ -12,6 +12,7 @@ enum Api {
   getRoleListByParam = '/zfb-api/zfb/shop/sys/user/getRoleListByParam',
   staffPermSave = '/zfb-api/zfb/shop/staffPerm/save',
   staffPermBind = '/zfb-api/zfb/shop/staffPerm/bind',
+  staffPermUpdate = '/zfb-api/zfb/shop/staffPerm/update',
   bindStaffList = '/zfb-api/zfb/shop/staffPerm/bindStaffList',
   cameraBind = '/zfb-api/zfb/cameraIncrement/bindCamera',
   staffPermUnbind = '/zfb-api/zfb/shop/staffPerm/unbind',
@@ -159,6 +160,15 @@ export const unbindCamera = (params) =>
       ignoreCancelToken: true,
     },
   });
+export const staffPermUpdate = (params) =>
+  defHttp.post<Result>({
+    url: Api.staffPermUpdate,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
 export const bindRights = (params) =>
   defHttp.post<Result>({
     url: Api.staffPermBind,

+ 27 - 0
src/api/scene/list.ts

@@ -22,6 +22,8 @@ enum Api {
   dismissRoom = '/zfb-api/zfb/liveRoom/dismissRoom',
   deleteRoom = '/zfb-api/zfb/liveRoom/deleteRoom',
   generateDefaultLiveRoom = '/zfb-api/zfb/scene/generateDefaultLiveRoom',
+  generateReality = '/zfb-api/zfb/scene/createVrScene',
+  getShareList = '/zfb-api/zfb/scene/getVRShareLinks',
 }
 
 /**
@@ -134,3 +136,28 @@ export const generateDefaultLiveRoomApi = (params: SceneDownloadParam) =>
       ignoreCancelToken: true,
     },
   });
+
+export const generateRealityApi = (params: SceneDownloadParam) =>
+  defHttp.post<Result>({
+    url: Api.generateReality,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const getShareListApi = (params: SceneDownloadParam) =>
+  defHttp.post<Result>(
+    {
+      url: Api.getShareList,
+      params,
+      headers: {
+        // @ts-ignore
+        ignoreCancelToken: true,
+      },
+    },
+    {
+      useResult: true,
+    },
+  );

+ 1 - 2
src/components/Upload/src/UploadModal.vue

@@ -223,7 +223,6 @@
           );
           item.status = UploadResultStatus.SUCCESS;
           item.responseData = data;
-
           if (afterFetch && isFunction(afterFetch)) {
             item.responseData = (await afterFetch(data)) || data;
           }
@@ -284,7 +283,7 @@
         for (const item of fileListRef.value) {
           const { status, responseData } = item;
           if (status === UploadResultStatus.SUCCESS && responseData) {
-            fileList.push(responseData.message);
+            fileList.push(responseData.message || responseData.url);
           }
         }
         // 存在一个上传成功的即可保存

+ 10 - 0
src/views/dashboard/analysis/enterprise.vue

@@ -5,6 +5,9 @@
       <template #toolbar>
         <a-button type="primary" @click="handleExport">导出数据</a-button>
       </template>
+      <template #Time="{ record }">
+        <Time :value="record.createTime" mode="datetime" />
+      </template>
     </BasicTable>
     <!-- <div class="md:flex enter-y">
       <div class="md:w-1/2 enter-y">
@@ -33,6 +36,7 @@
     // bulletChatStaticsApi,
     companyChatExportApi,
   } from '/@/api/dashboard/analysis';
+  import { Time } from '/@/components/Time';
 
   import { ListApi as ListCorporationApi } from '/@/api/corporation/list';
   import { listRoomsApi } from '/@/api/scene/list';
@@ -67,6 +71,12 @@
       width: 120,
     },
     {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      slots: { customRender: 'Time' },
+      width: 150,
+    },
+    {
       title: '总观看人数',
       dataIndex: 'spectatorNum',
       // sorter: true,

+ 47 - 2
src/views/dashboard/analysis/index.vue

@@ -27,6 +27,15 @@
       <template #toolbar>
         <a-button type="primary" @click="handleExport">导出数据</a-button>
       </template>
+      <template #Time="{ record }">
+        <Time :value="record.createTime" mode="datetime" />
+      </template>
+      <template #Time1="{ record }">
+        <Time v-if="record.firstEnterRoomTime" :value="record.firstEnterRoomTime" mode="datetime" />
+      </template>
+      <template #Time2="{ record }">
+        <Time v-if="record.lastExistRoomTime" :value="record.lastExistRoomTime" mode="datetime" />
+      </template>
     </BasicTable>
     <!-- <div class="md:flex enter-y">
       <div class="md:w-1/2 enter-y">
@@ -53,6 +62,7 @@
   import { Card } from 'ant-design-vue';
   import VisitAnalysis from './components/VisitAnalysis.vue';
   import VisitAnalysisBar from './components/VisitAnalysisBar.vue';
+  import { Time } from '/@/components/Time';
   import {
     bulletChatApi,
     userStaticsApi,
@@ -92,6 +102,12 @@
       title: '手机号',
       dataIndex: 'userName',
       width: 120,
+      customRender: ({ record }) => {
+        const { userName } = record;
+        let reg = /(\d{3})\d{4}(\d{4})/;
+        const macthUserName = userName.replace(reg, '$1****$2');
+        return macthUserName;
+      },
     },
     {
       title: '房间名称',
@@ -104,6 +120,23 @@
       width: 120,
     },
     {
+      title: '初次进入房间',
+      dataIndex: 'firstEnterRoomTime',
+      slots: { customRender: 'Time1' },
+      width: 150,
+    },
+    {
+      title: '最后离开房间',
+      dataIndex: 'lastExistRoomTime',
+      slots: { customRender: 'Time2' },
+      width: 150,
+    },
+    {
+      title: '邀请者',
+      dataIndex: 'inviterUserName',
+      width: 120,
+    },
+    {
       title: '留言条数',
       dataIndex: 'bulletChatAmount',
       width: 120,
@@ -147,8 +180,8 @@
         component: 'RangePicker',
         defaultValue: [priorDate, today],
         colProps: {
-          xl: 16,
-          xxl: 16,
+          xl: 6,
+          xxl: 6,
         },
         componentProps: {
           format: 'YYYY-MM-DD',
@@ -184,6 +217,18 @@
           },
         ],
       },
+      {
+        field: 'inviterUserName',
+        label: '邀请者',
+        component: 'Input',
+        componentProps: {
+          maxLength: 100,
+        },
+        colProps: {
+          xl: 5,
+          xxl: 5,
+        },
+      },
     ],
     resetFunc: handleReset,
   };

+ 2 - 1
src/views/product/drawer.data.ts

@@ -23,7 +23,8 @@ export const formSchema: FormSchema[] = [
       showSearch: true,
       optionFilterProp: 'label',
       filterTreeNode: (searchVal, treeNode) => {
-        return treeNode.title.includes(searchVal);
+        console.log('filterTreeNode', searchVal, treeNode);
+        return treeNode.name.includes(searchVal);
       },
       fieldNames: {
         label: 'name',

+ 1 - 1
src/views/product/goodsSpecs.vue

@@ -111,7 +111,7 @@
               showSearch: true,
               optionFilterProp: 'label',
               filterTreeNode: (searchVal, treeNode) => {
-                return treeNode.title.includes(searchVal);
+                return treeNode.name.includes(searchVal);
               },
               onChange: function (val, a) {
                 specsObj.value[val] = a[0];

+ 9 - 1
src/views/product/ref.vue

@@ -76,7 +76,7 @@
         },
       ];
 
-      const [registerTable, { expandAll, reload, collapseAll }] = useTable({
+      const [registerTable, { expandAll, reload, collapseAll, getDataSource }] = useTable({
         title: '商品属性',
         api: attributeListApi,
         columns: columns,
@@ -84,6 +84,14 @@
         showIndexColumn: false,
         // rowSelection: { type: 'checkbox' },
         // rowKey: 'id',
+        beforeFetch: async () => {
+          let EditKeyRef = currentEditKeyRef.value;
+          if (EditKeyRef) {
+            let record: EditRecordRow = await getDataSource()?.find((ele) => ele.key == EditKeyRef);
+            record.onEdit?.(false, false);
+            currentEditKeyRef.value = '';
+          }
+        },
         bordered: true,
         fetchSetting: {
           pageField: 'page',

+ 158 - 0
src/views/rightsEnterprises/EditModal.vue

@@ -0,0 +1,158 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    :title="title"
+    @ok="handleOk"
+    min-height="350"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed, reactive } from 'vue';
+  import { staffPermUpdate, bindStaffList } from '/@/api/rightsEnterprises/list'; //roleLIstApi
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+  import dayjs from 'dayjs';
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok', 'register'],
+    setup(_, context) {
+      const modelRef = ref({
+        isSee: false,
+      });
+      const userStore = useUserStore();
+      const userIdList = reactive({
+        list: [],
+      });
+      const userinfo = computed(() => userStore.getUserInfo);
+      const { companyId } = userinfo.value;
+      console.log('companyId', companyId);
+      const schemas: FormSchema[] = [
+        {
+          field: 'permName',
+          label: t('routes.rightsEnterprises.permName'),
+          slot: 'text',
+          component: 'Input',
+        },
+        {
+          field: 'expirationTime',
+          component: 'DatePicker',
+          label: '权益过期时间',
+          colProps: {
+            span: 22,
+          },
+          componentProps: {
+            valueFormat: 'YYYY-MM-DD HH:mm:ss',
+            format: 'YYYY-MM-DD HH:mm:ss',
+            showTime: {
+              defaultValue: dayjs('00:00:00', 'HH:mm:ss'),
+            },
+            disabledDate: (current) => {
+              return current && current < dayjs().endOf('day');
+            },
+            // disabledDateTime:()=>{
+            //   return {
+            //     disabledHours: () => range(0, 24).splice(4, 20),
+            //     disabledMinutes: () => range(30, 60),
+            //     disabledSeconds: () => [55, 56],
+            //   };
+            // }
+          },
+        },
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+      ];
+      const title = ref('修改权益');
+      const { createMessage } = useMessage();
+      const [registerForm, { setFieldsValue, validate, updateSchema, resetFields }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function onDataReceive(data) {
+        // 方式1;
+        let list = await bindStaffList({
+          companyId: data.companyId,
+          staffPermId: data.id,
+        });
+        userIdList.list = list.map((ele) => {
+          return {
+            ...ele,
+            label: ele.staffName,
+            value: ele.staffId,
+            key: ele.staffPhone,
+          };
+        });
+        console.log('handleDelete', userIdList.list);
+        modelRef.value.isSee = data.userId ? true : false;
+        title.value = data.userId ? '查 看' : '绑 定';
+        setFieldsValue({
+          ...data,
+          userId: data.userId ? data.staffName : data.userId,
+        });
+        let setSchema = [
+          {
+            field: 'userId',
+            required: !data.userId,
+            slot: !!data.userId ? 'text' : false,
+          },
+          {
+            field: 'userId',
+            componentProps: {
+              options: userIdList.list,
+            },
+          },
+        ];
+        updateSchema(setSchema);
+      }
+      async function handleOk() {
+        // if (modelRef.value.isSee) {
+        //   return closeModal();
+        // }
+        let data = await validate();
+        let res = await staffPermUpdate(data);
+        context && context.emit('update', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return {
+        register,
+        title,
+        schemas,
+        registerForm,
+        modelRef,
+        handleOk,
+        resetFields,
+      };
+    },
+  });
+</script>

+ 16 - 1
src/views/rightsEnterprises/list.vue

@@ -13,6 +13,7 @@
         <Time :value="record[field]" mode="datetime" />
       </template>
       <!-- onClick: handleOpenModal.bind(null, record), -->
+      <!-- ifShow: getCheckRole([RoleEnum.COMPANY_SHOOTER,RoleEnum.COMPANY_VIEWER, RoleEnum.COMPANY_SHOOTER, RoleEnum.PLAT_ADMIN]), -->
       <template #action="{ record }">
         <TableAction
           :actions="[
@@ -22,6 +23,11 @@
               onClick: handleDelete.bind(null, record),
             },
             {
+              label: '编辑',
+              ifShow: record.state != 2 && getCheckRole([RoleEnum.PLAT_ADMIN]),
+              onClick: handleEditTime.bind(null, record),
+            },
+            {
               label: '绑定',
               color: 'success',
               ifShow: (record.state == 0 || record.state == 1) && !record.userId,
@@ -52,6 +58,7 @@
     </BasicTable>
     <addModal @register="register" @update="reload" />
     <BindModal @register="registerDelList" @update="reload" />
+    <EditModal @register="registerEditList" @update="reload" />
   </div>
 </template>
 <script lang="ts">
@@ -61,6 +68,7 @@
   import { useModal } from '/@/components/Modal';
   import { uploadApi } from '/@/api/sys/upload';
   import BindModal from './BindModal.vue';
+  import EditModal from './EditModal.vue';
   import addModal from './addModal.vue';
   import { staffList, unbindRights, deleteRights } from '/@/api/rightsEnterprises/list';
   import { useI18n } from '/@/hooks/web/useI18n';
@@ -78,6 +86,7 @@
       Time,
       BindModal,
       addModal,
+      EditModal,
       // DelListModal,
     },
     props: {
@@ -97,6 +106,7 @@
       });
       const [registerDetail, { openModal: openDetaileModal }] = useModal();
       const [registerDelList, { openModal: openDelListeModal }] = useModal();
+      const [registerEditList, { openModal: openEditModal }] = useModal();
       const { createConfirm, createMessage } = useMessage();
       const userStore = useUserStore();
       const { getCheckRole } = userStore;
@@ -186,7 +196,7 @@
           ifShow: !getCheckRole('tourist'),
           slots: { customRender: 'action' },
           fixed: 'right',
-          width: 100,
+          width: 150,
         },
       ];
 
@@ -282,6 +292,9 @@
           ...modelRef,
         });
       }
+      async function handleEditTime(record) {
+        return openEditModal(true, record);
+      }
       async function deleteConfirm(record) {
         let res = await deleteRights({ id: record.id });
         console.log('deleteRights', res);
@@ -303,6 +316,7 @@
         registerDetail,
         registerDelList,
         openDelListeModal,
+        registerEditList,
         createMessage,
         modelRef,
         handleUpBind,
@@ -310,6 +324,7 @@
         reload,
         go,
         renderStatus,
+        handleEditTime,
         handleCreate,
         handleOpenModal,
         register,

+ 38 - 1
src/views/scenes/list.vue

@@ -47,6 +47,18 @@
                       (record.lived !== true && getCheckRole(['super', 'plat_admin'])),
                     onClick: generateDefaultLiveRoom.bind(null, record),
                   },
+                  {
+                    color: 'warning',
+                    label: '虚实同步',
+                    ifShow: true,
+                    onClick: syncReality.bind(null, record),
+                  },
+                  {
+                    color: 'error',
+                    label: '分享',
+                    ifShow: true,
+                    onClick: openShareModalFn.bind(null, record),
+                  },
                 ]
               : []
           "
@@ -55,6 +67,7 @@
     </BasicTable>
     <DownloadModal @register="registerDownloadModal" />
     <EditorModal @register="registerEditorModal" @reload="reload" />
+    <ShareModal @register="registerShareModal" @reload="reload" />
   </div>
 </template>
 <script lang="ts">
@@ -77,6 +90,7 @@
     // generateSceneEditTokenApi,
     downloadSceneDataAPi,
     generateDefaultLiveRoomApi,
+    generateRealityApi,
   } from '/@/api/scene/list';
   import { useI18n } from '/@/hooks/web/useI18n';
   import {
@@ -89,14 +103,16 @@
   import { useUserStore } from '/@/store/modules/user';
   import DownloadModal from './downloadModal.vue';
   import EditorModal from './editorModal.vue';
+  import ShareModal from './shareModal.vue';
 
   export default defineComponent({
-    components: { BasicTable, TableAction, TableImg, DownloadModal, EditorModal },
+    components: { BasicTable, TableAction, TableImg, DownloadModal, EditorModal, ShareModal },
     emits: ['register'],
     setup() {
       const { createMessage } = useMessage();
       const [registerDownloadModal, { openModal: openDownloadModal }] = useModal();
       const [registerEditorModal, { openModal: openEditorModal }] = useModal();
+      const [registerShareModal, { openModal: openShareModal }] = useModal();
       const { t } = useI18n();
       const userStore = useUserStore();
       const { getCheckRole, getEquity } = userStore;
@@ -305,6 +321,19 @@
           reload();
         }
       }
+      async function syncReality(record: Recordable) {
+        let res = await generateRealityApi({
+          sceneNum: record.num,
+        });
+        if (res) {
+          createMessage.success(t('common.optSuccess'));
+          reload();
+        }
+      }
+      function openShareModalFn(record: Recordable) {
+        openShareModal(true, record);
+      }
+
       return {
         registerTable,
         createMessage,
@@ -319,7 +348,15 @@
         openSceneEditorModal,
         getEquity,
         getCheckRole,
+        syncReality,
+        registerShareModal,
+        openShareModalFn,
       };
     },
   });
 </script>
+<style>
+  .vben-basic-table-action {
+    flex-wrap: wrap;
+  }
+</style>

+ 85 - 0
src/views/scenes/shareModal.vue

@@ -0,0 +1,85 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="handleCancel"
+    @register="register"
+    title="分享场景"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px"> </div>
+    <div v-for="item in lists" :key="item.sceneName" class="pt-8">
+      <div class="flex flex-nowrap flex-row justify-between py-2">
+        <div> {{ item.sceneName }}</div>
+        <a-button size="small" type="link" @click="handleCopy(item.webSite)">复制链接</a-button>
+      </div>
+      <Input :value="item.webSite" :disabled="true" />
+    </div>
+    <div v-if="empty" class="flex justify-center items-center" style="min-height: 200px">
+      暂无数据
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  // import { UpdateApi } from '/@/api/scene/list';
+  import { getShareListApi } from '/@/api/scene/list';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { Input } from 'ant-design-vue';
+  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  // import { BasicForm, FormSchema } from '/@/components/Form/index';
+  // import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const { createMessage } = useMessage();
+  // const { t } = useI18n();
+
+  interface ShareItemType {
+    sceneName: string;
+    webSite: string;
+  }
+  export default defineComponent({
+    components: { BasicModal, Input },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['reload', 'register'],
+    setup() {
+      const lists = ref<ShareItemType[]>([]);
+      const empty = ref<boolean>(false);
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function onDataReceive(data) {
+        // data = unref(data);
+        const res = await getShareListApi({
+          sceneNum: data.num,
+        });
+        console.log('res', res);
+
+        if (res.result?.length as ShareItemType[]) {
+          const data: ShareItemType[] = res.result;
+          lists.value = data;
+        } else {
+          empty.value = true;
+        }
+
+        // console.log('demoData', demoData);
+      }
+
+      async function handleOk() {
+        closeModal();
+        empty.value = false;
+      }
+      async function handleCancel() {
+        closeModal();
+        empty.value = false;
+      }
+
+      function handleCopy(text) {
+        const { isSuccessRef } = useCopyToClipboard(text);
+        unref(isSuccessRef) && createMessage.success('复制成功!');
+      }
+
+      return { register, handleOk, closeModal, lists, handleCopy, empty, handleCancel };
+    },
+  });
+</script>