Ver código fonte

releaseTime 上传

tangning 3 anos atrás
pai
commit
63dfcedaab

+ 1 - 0
package.json

@@ -41,6 +41,7 @@
     "@zxcvbn-ts/core": "^1.0.0-beta.0",
     "ant-design-vue": "2.2.8",
     "axios": "^0.24.0",
+    "cropperjs": "^1.5.12",
     "crypto-js": "^4.1.1",
     "echarts": "^5.2.2",
     "lodash-es": "^4.17.21",

+ 58 - 3
src/api/product/index.ts

@@ -13,11 +13,13 @@ enum Api {
   spaceSdkDel = '/newV4/service/manage/spaceSdk/delete',
   addAndUpload = '/newV4/service/manage/cameraVersion/addAndUpload',
   editAndUpload = '/newV4/service/manage/cameraVersion/update',
-  // spaceSdkList = '/newV4/service/manage/spaceSdk/list',
+  appFileAgentList = '/newV4/service/manage/appFile/agentList',
   spaceSdkUpload = '/newV4/service/manage/spaceSdk/upload',
-  // spaceSdkOnline = '/newV4/service/manage/spaceSdk/online',
-  // spaceSdkTop = '/newV4/service/manage/spaceSdk/top',
+  appFileAdd = '/newV4/service/manage/appFile/add',
+  appFileUpload = '/newV4/service/manage/appFile/upload',
   spaceSdkDelete = '/newV4/service/manage/spaceSdk/delete',
+  appFileList = '/newV4/service/manage/appFile/list',
+  appFileDelete = '/newV4/service/manage/appFile/delete'
 }
 
 /**
@@ -132,7 +134,47 @@ export const SpaceSdkDelete = (params: updateParams) =>
      ignoreCancelToken: true,
    },
  });
+//app 管理
+export const AppFileAdd = (params: updateParams) =>
+ defHttp.post<Result>({
+   url: Api.appFileAdd,
+   params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
 
+ export const AppFileAgentList = (params: updateParams) =>
+ defHttp.get<Result>({
+   url: Api.appFileAgentList,
+   params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+export const AppFileList = (params: updateParams) =>
+ defHttp.post<Result>({
+   url: Api.appFileList,
+   params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ 
+export const AppFileDelete = (params: updateParams) =>
+defHttp.post<Result>({
+  url: Api.appFileDelete,
+  params,
+  headers: {
+    // @ts-ignore
+    ignoreCancelToken: true,
+  },
+});
+//上传文件
 export function SpaceSdkUpload(
   params: UploadFileParams,
   onUploadProgress: (progressEvent: ProgressEvent) => void,
@@ -156,4 +198,17 @@ export function uploadApi(
     },
     params,
   );
+ }
+ 
+export function AppFileUpload(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: ProgressEvent) => void,
+) {
+  return defHttp.uploadFile<Result>(
+    {
+      url: Api.appFileUpload,
+      onUploadProgress,
+    },
+    params,
+  );
  }

+ 7 - 0
src/locales/lang/zh-CN/routes/account.ts

@@ -0,0 +1,7 @@
+export default {
+  type:{
+    1:'权益订单',
+    2:'下载订单',
+    3:'消费订单',
+  }
+};

+ 4 - 0
src/locales/lang/zh-CN/routes/dashboard.ts

@@ -30,6 +30,10 @@ export default {
   devices: '设备管理',
   product: '产品管理',
   firmware: '固件管理',
+  account:'用户管理',
+  overview: '用户概览',
+  details: '订单详情',
+  productData: '产品数据',
   sdk: 'Space Target SDK',
   app: 'App管理',
   productRef: '商品属性',

+ 43 - 0
src/router/routes/modules/account.ts

@@ -0,0 +1,43 @@
+import type { AppRouteRecordRaw } from '/@/router/types';
+import { t } from '/@/hooks/web/useI18n';
+import { LAYOUT } from '/@/router/constant';
+
+export const accountRoute: AppRouteRecordRaw = {
+  path: '/account',
+  name: 'Account',
+  redirect: '/account/overview',
+  component: LAYOUT,
+  meta: {
+    title: t('routes.dashboard.account'),
+    icon: 'la:file-invoice-dollar',
+    // hideChildrenInMenu: true,
+    orderNo: 190,
+  },
+  children: [
+    {
+      path: 'overview',
+      name: 'Overview',
+      component: () => import('/@/views/account/overview/index.vue'),
+      meta: {
+        title: t('routes.dashboard.overview'),
+      },
+    },
+    {
+      path: 'details',
+      name: 'Details',
+      component: () => import('/@/views/account/details/index.vue'),
+      meta: {
+        title: t('routes.dashboard.details'),
+      },
+    },
+    {
+      path: 'product',
+      name: 'Product',
+      component: () => import('/@/views/account/product/index.vue'),
+      meta: {
+        title: t('routes.dashboard.productData'),
+      },
+    },
+  ],
+};
+export default accountRoute;

+ 59 - 0
src/views/account/AccountBind.vue

@@ -0,0 +1,59 @@
+<template>
+  <CollapseContainer title="账号绑定" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #avatar>
+              <Icon v-if="item.avatar" class="avatar" :icon="item.avatar" :color="item.color" />
+            </template>
+            <template #title>
+              {{ item.title }}
+              <a-button type="link" size="small" v-if="item.extra" class="extra">
+                {{ item.extra }}
+              </a-button>
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import Icon from '/@/components/Icon/index';
+
+  import { accountBindList } from './data';
+
+  export default defineComponent({
+    components: {
+      CollapseContainer,
+      List,
+      ListItem: List.Item,
+      ListItemMeta: List.Item.Meta,
+      Icon,
+    },
+    setup() {
+      return {
+        list: accountBindList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .avatar {
+    font-size: 40px !important;
+  }
+
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+    cursor: pointer;
+  }
+</style>

+ 95 - 0
src/views/account/BaseSetting.vue

@@ -0,0 +1,95 @@
+<template>
+  <CollapseContainer title="基本设置" :canExpan="false">
+    <a-row :gutter="24">
+      <a-col :span="14">
+        <BasicForm @register="register" />
+      </a-col>
+      <a-col :span="10">
+        <div class="change-avatar">
+          <div class="mb-2">头像</div>
+          <CropperAvatar
+            :uploadApi="uploadApi"
+            :value="avatar"
+            btnText="更换头像"
+            :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
+            @change="updateAvatar"
+            width="150"
+          />
+        </div>
+      </a-col>
+    </a-row>
+    <Button type="primary" @click="handleSubmit"> 更新基本信息 </Button>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { Button, Row, Col } from 'ant-design-vue';
+  import { computed, defineComponent, onMounted } from 'vue';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { CollapseContainer } from '/@/components/Container';
+  import { CropperAvatar } from '/@/components/Cropper';
+
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  import headerImg from '/@/assets/images/header.jpg';
+  import { accountInfoApi } from '/@/api/demo/account';
+  import { baseSetschemas } from './data';
+  import { useUserStore } from '/@/store/modules/user';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  export default defineComponent({
+    components: {
+      BasicForm,
+      CollapseContainer,
+      Button,
+      ARow: Row,
+      ACol: Col,
+      CropperAvatar,
+    },
+    setup() {
+      const { createMessage } = useMessage();
+      const userStore = useUserStore();
+
+      const [register, { setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas: baseSetschemas,
+        showActionButtonGroup: false,
+      });
+
+      onMounted(async () => {
+        const data = await accountInfoApi();
+        setFieldsValue(data);
+      });
+
+      const avatar = computed(() => {
+        const { avatar } = userStore.getUserInfo;
+        return avatar || headerImg;
+      });
+
+      function updateAvatar(src: string) {
+        const userinfo = userStore.getUserInfo;
+        userinfo.avatar = src;
+        userStore.setUserInfo(userinfo);
+      }
+
+      return {
+        avatar,
+        register,
+        uploadApi: uploadApi as any,
+        updateAvatar,
+        handleSubmit: () => {
+          createMessage.success('更新成功!');
+        },
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .change-avatar {
+    img {
+      display: block;
+      margin-bottom: 15px;
+      border-radius: 50%;
+    }
+  }
+</style>

+ 53 - 0
src/views/account/MsgNotify.vue

@@ -0,0 +1,53 @@
+<template>
+  <CollapseContainer title="新消息通知" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #title>
+              {{ item.title }}
+              <Switch
+                class="extra"
+                checked-children="开"
+                un-checked-children="关"
+                default-checked
+              />
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List, Switch } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  import { msgNotifyList } from './data';
+
+  export default defineComponent({
+    components: {
+      CollapseContainer,
+      List,
+      ListItem: List.Item,
+      ListItemMeta: List.Item.Meta,
+      Switch,
+    },
+    setup() {
+      return {
+        list: msgNotifyList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+  }
+</style>

+ 47 - 0
src/views/account/SecureSetting.vue

@@ -0,0 +1,47 @@
+<template>
+  <CollapseContainer title="安全设置" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #title>
+              {{ item.title }}
+              <div class="extra" v-if="item.extra">
+                {{ item.extra }}
+              </div>
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  import { secureSettingList } from './data';
+
+  export default defineComponent({
+    components: { CollapseContainer, List, ListItem: List.Item, ListItemMeta: List.Item.Meta },
+    setup() {
+      return {
+        list: secureSettingList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+    font-weight: normal;
+    color: #1890ff;
+    cursor: pointer;
+  }
+</style>

+ 198 - 0
src/views/account/data.ts

@@ -0,0 +1,198 @@
+import { FormSchema } from '/@/components/Form/index';
+import { BasicColumn } from '/@/components/Table/src/types/table';
+import { Time } from '/@/components/Time';
+import { h } from 'vue';
+
+export interface ListItem {
+  key: string;
+  title: string;
+  description: string;
+  extra?: string;
+  avatar?: string;
+  color?: string;
+}
+
+// tab的list
+export const settingList = [
+  {
+    key: '1',
+    name: '基本设置',
+    component: 'BaseSetting',
+  },
+  {
+    key: '2',
+    name: '安全设置',
+    component: 'SecureSetting',
+  },
+  {
+    key: '3',
+    name: '账号绑定',
+    component: 'AccountBind',
+  },
+  {
+    key: '4',
+    name: '新消息通知',
+    component: 'MsgNotify',
+  },
+];
+
+// 基础设置 form
+export const baseSetschemas: FormSchema[] = [
+  {
+    field: 'email',
+    component: 'Input',
+    label: '下单时间',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'name',
+    component: 'Input',
+    label: '昵称',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'introduction',
+    component: 'InputTextArea',
+    label: '个人简介',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'phone',
+    component: 'Input',
+    label: '联系电话',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'address',
+    component: 'Input',
+    label: '所在地区',
+    colProps: { span: 18 },
+  },
+];
+
+// 安全设置 list
+export const secureSettingList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '当前密码强度::强',
+    extra: '修改',
+  },
+  {
+    key: '2',
+    title: '密保手机',
+    description: '已绑定手机::138****8293',
+    extra: '修改',
+  },
+  {
+    key: '3',
+    title: '密保问题',
+    description: '未设置密保问题,密保问题可有效保护账户安全',
+    extra: '修改',
+  },
+  {
+    key: '4',
+    title: '备用邮箱',
+    description: '已绑定邮箱::ant***sign.com',
+    extra: '修改',
+  },
+  {
+    key: '5',
+    title: 'MFA 设备',
+    description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
+    extra: '修改',
+  },
+];
+
+// 账号绑定 list
+export const accountBindList: ListItem[] = [
+  {
+    key: '1',
+    title: '绑定淘宝',
+    description: '当前未绑定淘宝账号',
+    extra: '绑定',
+    avatar: 'ri:taobao-fill',
+    color: '#ff4000',
+  },
+  {
+    key: '2',
+    title: '绑定支付宝',
+    description: '当前未绑定支付宝账号',
+    extra: '绑定',
+    avatar: 'fa-brands:alipay',
+    color: '#2eabff',
+  },
+  {
+    key: '3',
+    title: '绑定钉钉',
+    description: '当前未绑定钉钉账号',
+    extra: '绑定',
+    avatar: 'ri:dingding-fill',
+    color: '#2eabff',
+  },
+];
+
+// 新消息通知 list
+export const msgNotifyList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '其他用户的消息将以站内信的形式通知',
+  },
+  {
+    key: '2',
+    title: '系统消息',
+    description: '系统消息将以站内信的形式通知',
+  },
+  {
+    key: '3',
+    title: '待办任务',
+    description: '待办任务将以站内信的形式通知',
+  },
+];
+export const refundTimeTableSchema: BasicColumn[] = [
+  {
+    title: '下单时间',
+    width: 150,
+    dataIndex: 'createTime',
+    customRender: ({ record }) => {
+      return (
+        record.createTime &&
+        h(Time, {
+          value: record.createTime,
+          mode: 'datetime',
+        })
+      );
+    },
+  },{
+    title: '订单号',
+    width: 150,
+    dataIndex: 'version',
+  },
+  {
+    title: '用户账号',
+    width: 150,
+    dataIndex: 'description',
+  },
+  {
+    title: '数量',
+    width: 150,
+    dataIndex: 'minVersion',
+  },{
+    title: '订单金额(元)',
+    width: 150,
+    dataIndex: 'minVersion',
+  },{
+    title: '支付方式',
+    width: 150,
+    dataIndex: 'recStatus',
+  },{
+    title: '交易号',
+    width: 150,
+    dataIndex: 'minVersion',
+  },{
+    title: '订单状态',
+    width: 150,
+    dataIndex: 'recStatus',
+  },
+];

+ 259 - 0
src/views/account/details/AddModal.vue

@@ -0,0 +1,259 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="新增相机"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm" :model="model" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi, SpaceSdkUpload } from '/@/api/product/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const modelRef = ref({});
+      const fileFlow = reactive({
+        file:null
+      })
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'type',
+          component: 'Input',
+          label: t('routes.product.types'),
+          slot: 'text',
+          colProps: {
+            span: 24,
+          },
+          // required: true,
+        },
+        {
+          field: 'version',
+          component: 'Input',
+          label: t('routes.product.version'),
+          required: true,
+          colProps: {
+            span: 24,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.version'));
+                }
+                if(/.*[\u4e00-\u9fa5]+.*$/.test(value)){
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps: {
+            maxLength: 15,
+            onChange: (data) => {
+              console.log('data', data);
+            },
+          },
+        },
+        // {
+        //   field: 'minVersion',
+        //   component: 'Input',
+        //   label: t('routes.product.minVersion'),
+        //   required: true,
+        //   rules: [
+        //     {
+        //       required: true,
+        //       // @ts-ignore
+        //       validator: async (rule, value) => {
+        //         const regPos = /.*[\u4e00-\u9fa5]+.*$/; // 非中文
+        //         if (!value) {
+        //           return Promise.reject(t('common.inputText')+t('routes.product.minVersion'));
+        //         }
+        //         if (regPos.test(value)) {
+        //           /* eslint-disable-next-line */
+        //           return Promise.reject('不支持中文字符');
+        //         }
+        //         return Promise.resolve();
+        //       },
+        //       trigger: 'change',
+        //     },
+        //   ],
+        //   colProps: {
+        //     span: 24,
+        //   },
+        // },
+        {
+          field: 'imprintCh',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description_zh'),
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'imprintEn',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description_en'),
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'file',
+          component: 'Upload',
+          label: t('routes.product.file'),
+          required: true,
+          rules: [{ required: true, message: t('common.uploadMessge') }],
+          // helpMessage: t('routes.corporation.uploadHelp'),
+          itemProps: {
+            validateTrigger: 'onBlur',
+          },
+          componentProps: {
+            api: uploadApi,
+            maxNumber: 1,
+            maxSize: 1000,
+            fileFlow:true,
+            accept: ['4dage','jpg','png'],
+            afterFetch: function (data) {
+              console.log('uploadApi',data)
+              Reflect.set(data, 'url', data.file);
+              fileFlow.file = data.file
+              return data;
+            },
+          },
+
+          colProps: {
+            span: 22,
+          },
+        },
+        {
+          field: 'isTop',
+          component: 'CheckboxGroup',
+          label: '官网置顶',
+          componentProps: {
+            options: [
+              { label: '(勾选此项,将在四维看看官网置顶)', value: 1 },
+            ],
+          },
+          colProps: {
+            span: 24,
+          },
+        },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function renderOwnTypeLabel(type: number): string {
+        switch (type) {
+          case 2:
+            return t('routes.product.sdkType.2');
+          case 1:
+            return t('routes.product.sdkType.1');
+          case 3:
+            return t('routes.product.type.3');
+          default:
+            return '';
+        }
+      }
+      function rendercameraTypeLabel(cameraType: number): string {
+        switch (cameraType) {
+          case 4:
+            return t('routes.devices.cameraName.4');
+          case 1:
+            return t('routes.devices.cameraName.1');
+          case 9:
+            return t('routes.devices.cameraName.9');
+          case 10:
+            return t('routes.devices.cameraName.10');
+          case 6:
+            return t('routes.devices.cameraName.6');
+          default:
+            return '';
+        }
+      }
+      function onDataReceive(data) {
+        modelRef.value = data
+        resetFields();
+        setFieldsValue({
+          type:renderOwnTypeLabel(Number(data)),
+        });
+      }
+      const handleSubmit = async () => {
+        try {
+          const params = await validate();
+          const apiData = {
+            data:{
+            ...params as any,
+            platformType:modelRef.value,
+            file:fileFlow.file,
+            isTop:params.isTop && params.isTop[0] || 0
+            }
+          }
+          console.log('res', apiData);
+          const res = await SpaceSdkUpload(apiData);
+          console.log('res', res);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        rendercameraTypeLabel,
+        renderOwnTypeLabel,
+        schemas,
+        registerForm,
+        model: modelRef,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 236 - 0
src/views/account/details/EditModal.vue

@@ -0,0 +1,236 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="编辑相机"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >Checkbox
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm" :model="model" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+        <template #Checkbox="{ model, field }">
+          <div>
+            <Checkbox v-model:checked="model[field]">(勾选此项,将在四维看看官网置顶)</Checkbox>
+          </div>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { Checkbox  } from 'ant-design-vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi, SpaceSdkUpdate } from '/@/api/product/index';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm, Checkbox },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const modelRef = ref({});
+      const fileFlow = reactive({
+        file:null
+      })
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'fileUrl',
+          component: 'Input',
+          label: t('routes.product.types'),
+          show: false,
+          colProps: {
+            span: 24,
+          },
+        },{
+          field: 'id',
+          component: 'Input',
+          label: t('routes.product.types'),
+          show: false,
+          colProps: {
+            span: 24,
+          },
+        },{
+          field: 'platformType',
+          component: 'Input',
+          label: t('routes.product.types'),
+          show: false,
+          colProps: {
+            span: 24,
+          },
+        },{
+          field: 'type',
+          component: 'Input',
+          label: t('routes.product.types'),
+          slot: 'text',
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'version',
+          component: 'Input',
+          label: t('routes.product.version'),
+          required: true,
+          colProps: {
+            span: 24,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.version'));
+                }
+                if(/.*[\u4e00-\u9fa5]+.*$/.test(value)){
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps: {
+            maxLength: 15,
+            onChange: (data) => {
+              console.log('data', data);
+            },
+          },
+        },
+        {
+          field: 'imprintCh',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description_zh'),
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'imprintEn',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description_en'),
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'isTop',
+          component: 'CheckboxGroup',
+          label: '官网置顶',
+          slot:'Checkbox',
+          componentProps: {
+            options: [
+              { label: '(勾选此项,将在四维看看官网置顶)', value: 1 },
+            ],
+          },
+          colProps: {
+            span: 24,
+          },
+        },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        console.log('useModalInner',data)
+        data && onDataReceive(data);
+      });
+      function renderOwnTypeLabel(type: number): string {
+        switch (type) {
+          case 2:
+            return t('routes.product.sdkType.2');
+          case 1:
+            return t('routes.product.sdkType.1');
+          case 3:
+            return t('routes.product.type.3');
+          default:
+            return '';
+        }
+      }
+      function rendercameraTypeLabel(cameraType: number): string {
+        switch (cameraType) {
+          case 4:
+            return t('routes.devices.cameraName.4');
+          case 1:
+            return t('routes.devices.cameraName.1');
+          case 9:
+            return t('routes.devices.cameraName.9');
+          case 10:
+            return t('routes.devices.cameraName.10');
+          case 6:
+            return t('routes.devices.cameraName.6');
+          default:
+            return '';
+        }
+      }
+      function onDataReceive(data) {
+        modelRef.value = data
+        let setData = {
+          ...data,
+          type:renderOwnTypeLabel(Number(data.type)),
+        }
+        console.log('onDataReceive',data,setData)
+        resetFields();
+        setFieldsValue(setData);
+      }
+      const handleSubmit = async () => {
+        try {
+          const params = await validate();
+          console.log('modelRef',params)
+          const apiData = {
+            // ...modelRef.value,
+            ...params as any,
+            isTop:params.isTop?1:0
+          }
+          console.log('res', apiData);
+          const res = await SpaceSdkUpdate(apiData);
+          console.log('res', res);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        rendercameraTypeLabel,
+        renderOwnTypeLabel,
+        schemas,
+        registerForm,
+        model: modelRef,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 151 - 0
src/views/account/details/index.vue

@@ -0,0 +1,151 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="searchInfo.type" @change="tabChange">
+        <a-tab-pane key="1" :tab="t('routes.account.type.1')" />
+        <a-tab-pane key="2" :tab="t('routes.account.type.2')" />
+        <a-tab-pane key="3" :tab="t('routes.account.type.3')" />
+      </a-tabs>
+    </template>
+    <div class="desc-wrap-BasicTable">
+      <BasicTable @register="registerTimeTable"></BasicTable>
+    </div>
+    <AddModal @update="reload" @register="registerAddModal" />
+    <EditModal @register="registerEditModal" @update="reload" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, h } from 'vue';
+import { BasicTable, useTable, FormProps, TableAction, BasicColumn } from '/@/components/Table';
+import { PageWrapper } from '/@/components/Page';
+import { Divider, Card, Empty, Descriptions, Steps, Tabs } from 'ant-design-vue';
+import { SpaceSdkList, SpaceSdkDelete, SpaceSdkOnline } from '/@/api/product';
+import { refundTimeTableSchema } from '../data';
+import { useModal } from '/@/components/Modal';
+import { useI18n } from '/@/hooks/web/useI18n';
+import AddModal from './AddModal.vue';
+import EditModal from './EditModal.vue';
+import { useMessage } from '/@/hooks/web/useMessage';
+export default defineComponent({
+  components: {
+    BasicTable,
+    AddModal,
+    EditModal,
+    TableAction,
+    PageWrapper,
+    [Divider.name]: Divider,
+    [Card.name]: Card,
+    Empty,
+    [Descriptions.name]: Descriptions,
+    [Descriptions.Item.name]: Descriptions.Item,
+    [Steps.name]: Steps,
+    [Steps.Step.name]: Steps.Step,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const searchInfo = reactive<Recordable>({
+      type: '1',
+    });
+    const [registerAddModal, { openModal: openAddModal }] = useModal();
+    const [registerEditModal, { openModal: openEditModal }] = useModal();
+    const { createMessage } = useMessage();
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 100,
+      schemas: [
+        {
+          field: 'sceneName',
+          label: t('routes.operate.releaseTime'),
+          component: 'RangePicker',
+          componentProps: {
+            maxLength: 100,
+            format: 'YYYY-MM-DD HH:mm',
+            showTime: true,
+          },
+          colProps: {
+            xl: 11,
+            xxl: 11,
+          },
+        },
+        {
+          field: 'version',
+          label: '订单号',
+          component: 'Input',
+          componentProps: {
+            maxLength: 100,
+          },
+          colProps: {
+            xl: 6,
+            xxl: 6,
+          },
+        },
+      ],
+    };
+    const [registerTimeTable, { reload }] = useTable({
+      api: SpaceSdkList,
+      title: '订单列表',
+      columns: refundTimeTableSchema,
+      useSearchForm: true,
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn:false,
+      rowKey: 'id',
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      searchInfo: searchInfo,
+      canResize: false,
+    });
+    function tabChange(val: string) {
+      console.log('tabChange', val);
+      reload();
+    }
+    async function handleDelete(record: Recordable) {
+      console.log('点击了删除', record);
+      await SpaceSdkDelete({ id: record.id });
+      createMessage.success(t('common.optSuccess'));
+      reload();
+    }
+    function handleOpen(record: Recordable) {
+      console.log('点击了启用', record);
+    }
+    async function handleOff(record: Recordable) {
+      await SpaceSdkOnline({ id: record.id, status:2 });//状态 0 - 未发布 1 -发布  2-下架
+      createMessage.success(t('common.optSuccess'));
+      reload();
+    }
+    function handleEdit(record: Recordable) {
+      console.log('record', record);
+      openEditModal(true, {
+        ...record,
+      });
+    }
+    return {
+      registerTimeTable,
+      handleDelete,
+      handleOpen,
+      tabChange,
+      handleOff,
+      reload,
+      registerAddModal,
+      registerEditModal,
+      openAddModal,
+      handleEdit,
+      t,
+      searchInfo,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 61 - 0
src/views/account/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <ScrollContainer>
+    <div ref="wrapperRef" :class="prefixCls">
+      <Tabs tab-position="left" :tabBarStyle="tabBarStyle">
+        <template v-for="item in settingList" :key="item.key">
+          <TabPane :tab="item.name">
+            <!-- <component :is="item.component" /> -->
+          </TabPane>
+        </template>
+      </Tabs>
+    </div>
+  </ScrollContainer>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Tabs } from 'ant-design-vue';
+
+  import { ScrollContainer } from '/@/components/Container/index';
+  import { settingList } from './data';
+
+  import BaseSetting from './BaseSetting.vue';
+  import SecureSetting from './SecureSetting.vue';
+  import AccountBind from './AccountBind.vue';
+  import MsgNotify from './MsgNotify.vue';
+
+  export default defineComponent({
+    components: {
+      ScrollContainer,
+      Tabs,
+      TabPane: Tabs.TabPane,
+      BaseSetting,
+      SecureSetting,
+      AccountBind,
+      MsgNotify,
+    },
+    setup() {
+      return {
+        prefixCls: 'account-setting',
+        settingList,
+        tabBarStyle: {
+          width: '220px',
+        },
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .account-setting {
+    margin: 12px;
+    background-color: @component-background;
+
+    .base-title {
+      padding-left: 0;
+    }
+
+    .ant-tabs-tab-active {
+      background-color: @item-active-bg;
+    }
+  }
+</style>

+ 202 - 0
src/views/account/overview/data.tsx

@@ -0,0 +1,202 @@
+import { DescItem } from '/@/components/Description/index';
+import { BasicColumn } from '/@/components/Table/src/types/table';
+import { Button } from '/@/components/Button';
+
+import { Badge } from 'ant-design-vue';
+
+export const refundData = {
+  a1: '1000000000',
+  a2: '已取货',
+  a3: '1234123421',
+  a4: '3214321432',
+  a5: '3214321432',
+  b1: '付小小',
+  b2: '18100000000',
+  b3: '菜鸟仓储',
+  b4: '浙江省杭州市西湖区万塘路18号',
+  b5: '无',
+};
+
+export const personData = {
+  b1: '付小小',
+  b2: '18100000000',
+  b3: '菜鸟仓储',
+  b4: '浙江省杭州市西湖区万塘路18号',
+  b5: '无',
+};
+export const refundSchema: DescItem[] = [
+  {
+    field: 'a1',
+    label: '昵称',
+  },
+  {
+    field: 'a2',
+    label: '账号',
+  },
+  {
+    field: 'a3',
+    label: '邮箱',
+  },
+  {
+    field: 'a4',
+    label: '注册时间',
+  },
+  {
+    field: 'a5',
+    label: '四维看看会员',
+  },
+];
+export const personSchema: DescItem[] = [
+  {
+    field: 'b1',
+    label: '权益数量',
+  },
+  {
+    field: 'b2',
+    label: '剩余下载次数',
+  },
+  {
+    field: 'b3',
+    label: '相机数量',
+  },
+  {
+    field: 'b4',
+    label: '场景数量',
+  },
+];
+
+export const refundTableSchema: BasicColumn[] = [
+  {
+    title: '商品编号',
+    width: 150,
+    dataIndex: 't1',
+    customRender: ({ record }) => {
+      return (
+        <Button type="link" size="small">
+          {() => record.t1}
+        </Button>
+      );
+    },
+  },
+  {
+    title: '商品名称',
+    width: 150,
+    dataIndex: 't2',
+  },
+  {
+    title: '商品条码',
+    width: 150,
+    dataIndex: 't3',
+  },
+  {
+    title: '单价	',
+    width: 150,
+    dataIndex: 't4',
+  },
+  {
+    title: '数量(件)	',
+    width: 150,
+    dataIndex: 't5',
+  },
+  {
+    title: '金额',
+    width: 150,
+    dataIndex: 't6',
+  },
+];
+export const refundTimeTableSchema: BasicColumn[] = [
+  {
+    title: '时间',
+    width: 150,
+    dataIndex: 't1',
+  },
+  {
+    title: '当前进度',
+    width: 150,
+    dataIndex: 't2',
+  },
+  {
+    title: '状态',
+    width: 150,
+    dataIndex: 't3',
+    customRender: ({ record }) => {
+      return <Badge status="success" text={record.t3} />;
+    },
+  },
+  {
+    title: '操作员ID	',
+    width: 150,
+    dataIndex: 't4',
+  },
+  {
+    title: '耗时',
+    width: 150,
+    dataIndex: 't5',
+  },
+];
+
+export const refundTableData: any[] = [
+  {
+    t1: 1234561,
+    t2: '矿泉水 550ml',
+    t3: '12421432143214321',
+    t4: '2.00',
+    t5: 1,
+    t6: 2.0,
+  },
+  {
+    t1: 1234562,
+    t2: '矿泉水 550ml',
+    t3: '12421432143214321',
+    t4: '2.00',
+    t5: 2,
+    t6: 2.0,
+  },
+  {
+    t1: 1234562,
+    t2: '矿泉水 550ml',
+    t3: '12421432143214321',
+    t4: '2.00',
+    t5: 2,
+    t6: 2.0,
+  },
+  {
+    t1: 1234562,
+    t2: '矿泉水 550ml',
+    t3: '12421432143214321',
+    t4: '2.00',
+    t5: 2,
+    t6: 2.0,
+  },
+];
+
+export const refundTimeTableData: any[] = [
+  {
+    t1: '2017-10-01 14:10',
+    t2: '联系客户',
+    t3: '进行中',
+    t4: '取货员 ID1234',
+    t5: '5mins',
+  },
+  {
+    t1: '2017-10-01 14:10',
+    t2: '取货员出发',
+    t3: '成功',
+    t4: '取货员 ID1234',
+    t5: '5mins',
+  },
+  {
+    t1: '2017-10-01 14:10',
+    t2: '取货员接单',
+    t3: '成功',
+    t4: '系统',
+    t5: '5mins',
+  },
+  {
+    t1: '2017-10-01 14:10',
+    t2: '申请审批通过',
+    t3: '成功',
+    t4: '用户',
+    t5: '1h',
+  },
+];

+ 83 - 0
src/views/account/overview/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="info">
+    <div class="infodetail">
+      <div class="title">基本信息</div>
+      <div class="list">
+          <div class="list_item" v-for="item in refundSchema" :key="item.field">
+            <div class="lable">{{item.label}}</div>
+            <div class="value">{{refundData[item.field]}}</div>
+          </div>
+      </div>
+    </div>
+    <a-divider />
+    <div class="infodetail">
+      <div class="title">基本信息</div>
+      <div class="list">
+          <div class="list_item" v-for="item in refundSchema" :key="item.field">
+            <div class="lable">{{item.label}}</div>
+            <div class="value">{{refundData[item.field]}}</div>
+          </div>
+      </div>
+    </div>
+    <a-divider />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Description } from '/@/components/Description/index';
+  import { BasicTable } from '/@/components/Table';
+  import { PageWrapper } from '/@/components/Page';
+  import { Divider } from 'ant-design-vue';
+
+  import {
+    refundSchema,
+    refundData,
+    personSchema,
+    personData,
+  } from './data';
+  export default defineComponent({
+    components: { Description, BasicTable, PageWrapper, [Divider.name]: Divider },
+    setup() {
+      return {
+        refundSchema,
+        refundData,
+        personSchema,
+        personData,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .info{
+    margin: 16px;
+    background-color: #fff;
+    padding: 12px 10px 6px;
+    .title{
+      min-height: 48px;
+      margin-bottom: -1px;
+      padding: 0 24px;
+      color: #000000d9;
+      font-weight: 500;
+      font-size: 16px;
+      background: transparent;
+      border-bottom: 1px solid #f0f0f0;
+      border-radius: 2px 2px 0 0;
+    }
+    .list{
+      padding: 0 24px;
+      .list_item{
+        display: flex;
+        padding-top: 12px;
+        .lable{
+          width: 100px;
+          text-align: right;
+          margin-right: 20px;
+        }
+      }
+    }
+  }
+  .desc-wrap {
+    padding: 16px;
+    background-color: @component-background;
+  }
+</style>

+ 266 - 0
src/views/account/product/AddModal.vue

@@ -0,0 +1,266 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="新增相机"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm" :model="model" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi, AddAndUpload } from '/@/api/product/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const modelRef = ref({});
+      const fileFlow = reactive({
+        file:null
+      })
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'type',
+          component: 'Input',
+          label: t('routes.product.types'),
+          slot: 'text',
+          colProps: {
+            span: 24,
+          },
+          // required: true,
+        },
+        {
+          field: 'version',
+          component: 'Input',
+          label: t('routes.product.version'),
+          required: true,
+          colProps: {
+            span: 24,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.version'));
+                }
+                if(/.*[\u4e00-\u9fa5]+.*$/.test(value)){
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps: {
+            maxLength: 15,
+            onChange: (data) => {
+              console.log('data', data);
+            },
+          },
+        },
+        {
+          field: 'minVersion',
+          component: 'Input',
+          label: t('routes.product.minVersion'),
+          required: true,
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                const regPos = /.*[\u4e00-\u9fa5]+.*$/; // 非中文
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.minVersion'));
+                }
+                if (regPos.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'description',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description'),
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'file',
+          component: 'Upload',
+          label: t('routes.product.file'),
+          required: true,
+          rules: [{ required: true, message: t('common.uploadMessge') }],
+          // helpMessage: t('routes.corporation.uploadHelp'),
+          itemProps: {
+            validateTrigger: 'onBlur',
+          },
+          componentProps: {
+            api: uploadApi,
+            maxNumber: 1,
+            maxSize: 1000,
+            fileFlow:true,
+            accept: ['4dage'],
+            afterFetch: function (data) {
+              console.log('uploadApi',data)
+              Reflect.set(data, 'url', data.file);
+              fileFlow.file = data.file
+              return data;
+            },
+          },
+
+          colProps: {
+            span: 22,
+          },
+        },
+        // {
+        //   field: 'orderSn',
+        //   component: 'Input',
+        //   label: t('routes.devices.orderSn'),
+        //   colProps: {
+        //     span: 24,
+        //   },
+        // },
+        // {
+        //   field: 'companyId',
+        //   component: 'ApiSelect',
+        //   label: t('routes.devices.companyId'),
+        //   itemProps: {
+        //     validateTrigger: 'blur',
+        //   },
+        //   componentProps: {
+        //     api: allCompanyApi,
+        //     numberToString: true,
+        //     labelField: 'name',
+        //     valueField: 'id',
+        //     immediate: true,
+        //     params: {
+        //       page: 1,
+        //       limit: 1000,
+        //     },
+        //   },
+        //   colProps: {
+        //     span: 24,
+        //   },
+        // },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function renderOwnTypeLabel(type: number): string {
+        switch (type) {
+          case 2:
+            return t('routes.product.type.2');
+          case 1:
+            return t('routes.product.type.1');
+          case 3:
+            return t('routes.product.type.3');
+          default:
+            return '';
+        }
+      }
+      function rendercameraTypeLabel(cameraType: number): string {
+        switch (cameraType) {
+          case 4:
+            return t('routes.devices.cameraName.4');
+          case 1:
+            return t('routes.devices.cameraName.1');
+          case 9:
+            return t('routes.devices.cameraName.9');
+          case 10:
+            return t('routes.devices.cameraName.10');
+          case 6:
+            return t('routes.devices.cameraName.6');
+          default:
+            return '';
+        }
+      }
+      function onDataReceive(data) {
+        modelRef.value = data
+        resetFields();
+        setFieldsValue({
+          type:renderOwnTypeLabel(Number(data)),
+        });
+      }
+      const handleSubmit = async () => {
+        try {
+          const params = await validate();
+          const apiData = {
+            data:{
+            ...params as any,
+            type:modelRef.value,
+            file:fileFlow.file,
+            }
+          }
+          console.log('res', apiData);
+          const res = await AddAndUpload(apiData);
+          console.log('res', res);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        rendercameraTypeLabel,
+        renderOwnTypeLabel,
+        schemas,
+        registerForm,
+        model: modelRef,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 246 - 0
src/views/account/product/EditModal.vue

@@ -0,0 +1,246 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="编辑相机"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm" :model="model" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { EditAndUpload, } from '/@/api/product/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const modelRef = ref({});
+      const fileFlow = reactive({
+        file:null
+      })
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          label: t('routes.product.types'),
+          show: false,
+          colProps: {
+            span: 24,
+          },
+        },{
+          field: 'type',
+          component: 'Input',
+          label: t('routes.product.types'),
+          show: false,
+          colProps: {
+            span: 24,
+          },
+        },{
+          field: 'name',
+          component: 'Input',
+          label: t('routes.product.types'),
+          slot: 'text',
+          colProps: {
+            span: 24,
+          },
+          // required: true,
+        },
+        {
+          field: 'version',
+          component: 'Input',
+          label: t('routes.product.version'),
+          required: true,
+          colProps: {
+            span: 24,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.version'));
+                }
+                if(/.*[\u4e00-\u9fa5]+.*$/.test(value)){
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps: {
+            maxLength: 15,
+            onChange: (data) => {
+              console.log('data', data);
+            },
+          },
+        },
+        {
+          field: 'minVersion',
+          component: 'Input',
+          label: t('routes.product.minVersion'),
+          required: true,
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                const regPos = /.*[\u4e00-\u9fa5]+.*$/; // 非中文
+                if (!value) {
+                  return Promise.reject(t('common.inputText')+t('routes.product.minVersion'));
+                }
+                if (regPos.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('不支持中文字符');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          colProps: {
+            span: 24,
+          },
+        },
+        {
+          field: 'description',
+          component: 'InputTextArea',
+          required: true,
+          label: t('routes.product.description'),
+          colProps: {
+            span: 24,
+          },
+        },
+        // {
+        //   field: 'file',
+        //   component: 'Upload',
+        //   label: t('routes.product.file'),
+        //   required: true,
+        //   rules: [{ required: true, message: t('common.uploadMessge') }],
+        //   itemProps: {
+        //     validateTrigger: 'onBlur',
+        //   },
+        //   componentProps: {
+        //     api: uploadApi,
+        //     maxNumber: 1,
+        //     maxSize: 1000,
+        //     fileFlow:true,
+        //     accept: ['4dage'],
+        //     afterFetch: function (data) {
+        //       console.log('uploadApi',data)
+        //       Reflect.set(data, 'url', data.file);
+        //       fileFlow.file = data.file
+        //       return data;
+        //     },
+        //   },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function renderOwnTypeLabel(type: number): string {
+        switch (type) {
+          case 2:
+            return t('routes.product.type.2');
+          case 1:
+            return t('routes.product.type.1');
+          case 3:
+            return t('routes.product.type.3');
+          default:
+            return '';
+        }
+      }
+      function rendercameraTypeLabel(cameraType: number): string {
+        switch (cameraType) {
+          case 4:
+            return t('routes.devices.cameraName.4');
+          case 1:
+            return t('routes.devices.cameraName.1');
+          case 9:
+            return t('routes.devices.cameraName.9');
+          case 10:
+            return t('routes.devices.cameraName.10');
+          case 6:
+            return t('routes.devices.cameraName.6');
+          default:
+            return '';
+        }
+      }
+      function onDataReceive(data) {
+        modelRef.value = data
+        console.log('onDataReceive',data)
+        resetFields();
+        setFieldsValue({
+          ...data,
+          type:renderOwnTypeLabel(Number(data.type)),
+        });
+      }
+      const handleSubmit = async () => {
+        try {
+          const params = await validate();
+          console.log('modelRef',params)
+          const apiData = {
+            // ...modelRef.value,
+            ...params as any,
+          }
+          console.log('res', apiData);
+          const res = await EditAndUpload(apiData);
+          console.log('res', res);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        rendercameraTypeLabel,
+        renderOwnTypeLabel,
+        schemas,
+        registerForm,
+        model: modelRef,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 238 - 0
src/views/account/product/index.vue

@@ -0,0 +1,238 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="searchInfo.type" @change="tabChange">
+        <a-tab-pane key="1" :tab="t('routes.product.type.1')" />
+        <a-tab-pane key="2" :tab="t('routes.product.type.2')" />
+        <a-tab-pane key="3" :tab="t('routes.product.type.3')" />
+      </a-tabs>
+    </template>
+    <div class="desc-wrap-BasicTable">
+      <BasicTable @register="registerTimeTable">
+        <template #toolbar>
+          <a-button
+            type="primary"
+            @click="
+              () => {
+                openAddModal(true, searchInfo.type);
+              }
+            "
+            >{{ t('routes.product.addVstive') }}</a-button
+          >
+        </template>
+        <template #action="{ record }">
+          <TableAction
+            :actions="[
+              {
+                label: '编辑',
+                icon: 'ep:edit',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '删除',
+                icon: 'ic:outline-delete-outline',
+                popConfirm: {
+                  title: '是否确认删除',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </BasicTable>
+    </div>
+    <AddModal @update="reload" @register="registerAddModal" />
+    <EditModal @register="registerEditModal" @update="reload" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, h } from 'vue';
+import { BasicTable, useTable, FormProps, TableAction, BasicColumn } from '/@/components/Table';
+import { PageWrapper } from '/@/components/Page';
+import { Divider, Card, Empty, Descriptions, Steps, Tabs } from 'ant-design-vue';
+import { CameraList, DelAndUpload, EditAndUpload } from '/@/api/product';
+import { useModal } from '/@/components/Modal';
+import { useI18n } from '/@/hooks/web/useI18n';
+import AddModal from './AddModal.vue';
+import EditModal from './EditModal.vue';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { Switch } from 'ant-design-vue';
+import { Time } from '/@/components/Time';
+export default defineComponent({
+  components: {
+    BasicTable,
+    AddModal,
+    EditModal,
+    TableAction,
+    PageWrapper,
+    [Divider.name]: Divider,
+    [Card.name]: Card,
+    Empty,
+    [Descriptions.name]: Descriptions,
+    [Descriptions.Item.name]: Descriptions.Item,
+    [Steps.name]: Steps,
+    [Steps.Step.name]: Steps.Step,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const searchInfo = reactive<Recordable>({
+      type: '1',
+    });
+    const [registerAddModal, { openModal: openAddModal }] = useModal();
+    const [registerEditModal, { openModal: openEditModal }] = useModal();
+    const { createMessage } = useMessage();
+
+    const refundTimeTableSchema: BasicColumn[] = [
+      {
+        title: '版本号',
+        width: 150,
+        dataIndex: 'version',
+      },
+      {
+        title: '版本更新说明',
+        width: 150,
+        dataIndex: 'description',
+      },
+      {
+        title: '最低版本号',
+        width: 150,
+        dataIndex: 'minVersion',
+      },
+      {
+        title: '创建人',
+        width: 150,
+        dataIndex: 'recStatus',
+      },
+      {
+        title: '创建时间',
+        width: 150,
+        dataIndex: 'createTime',
+        customRender: ({ record }) => {
+          return (
+            record.createTime &&
+            h(Time, {
+              value: record.createTime,
+              mode: 'datetime',
+            })
+          );
+        },
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 80,
+        customRender: ({ record }) => {
+          if (!Reflect.has(record, 'pendingStatus')) {
+            record.pendingStatus = false;
+          }
+          return h(Switch, {
+            checked: record.status === 'A',
+            checkedChildren: '启用',
+            unCheckedChildren: '禁用',
+            loading: false,
+            onChange: async (checked: boolean) => {
+              record.pendingStatus = true;
+              const newStatus = checked ? 'A' : 'I';
+              await EditAndUpload({ ...record, status: newStatus });
+              if (checked) {
+                Reflect.set(record, 'status', newStatus);
+              } else {
+                Reflect.set(record, 'status', newStatus);
+              }
+              reload()
+              createMessage.success(t('common.optSuccess'));
+            },
+          });
+        },
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 100,
+      schemas: [
+        {
+          field: 'version',
+          label: '版本号',
+          component: 'Input',
+          componentProps: {
+            maxLength: 100,
+          },
+          colProps: {
+            xl: 6,
+            xxl: 6,
+          },
+        },
+      ],
+    };
+    const [registerTimeTable, { reload }] = useTable({
+      api: CameraList,
+      title: '相机列表',
+      columns: refundTimeTableSchema,
+      useSearchForm: true,
+      formConfig: searchForm,
+      showTableSetting: true,
+      rowKey: 'id',
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      searchInfo: searchInfo,
+      // beforeFetch:(T)=>{
+      //   T.type = searchInfo.type
+      //   console.log('beforeFetch',T,searchInfo)
+      //   return T
+      // },
+      actionColumn: {
+        width: 180,
+        title: '操作',
+        dataIndex: 'action',
+        slots: { customRender: 'action' },
+      },
+      canResize: false,
+    });
+    function tabChange(val: string) {
+      console.log('tabChange', val);
+      reload();
+    }
+    async function handleDelete(record: Recordable) {
+      console.log('点击了删除', record);
+      await DelAndUpload({ id: record.id });
+      createMessage.success(t('common.optSuccess'));
+      reload();
+    }
+    function handleOpen(record: Recordable) {
+      console.log('点击了启用', record);
+    }
+    function handleEdit(record: Recordable) {
+      console.log('record', record);
+      openEditModal(true, {
+        ...record,
+      });
+    }
+    return {
+      registerTimeTable,
+      handleDelete,
+      handleOpen,
+      tabChange,
+      reload,
+      registerAddModal,
+      registerEditModal,
+      openAddModal,
+      handleEdit,
+      t,
+      searchInfo,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 285 - 0
src/views/product/app/AddModal.vue

@@ -0,0 +1,285 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="新增APP"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm" :model="model" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, reactive, nextTick, onMounted } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi, AppFileAdd, AppFileUpload } from '/@/api/product/index';
+  import { TreeNode } from '/@/utils/treeUtils';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { AppFileAgentList,AppFileDelete } from '/@/api/product'
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const modelRef = ref({});
+      const uploadData = reactive({
+        md5:'',
+        name:'',
+        versionName:'',
+      })
+      const { createMessage,mes } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'fileServerType',
+          label: '上传服务器',
+          component: 'RadioButtonGroup',
+          defaultValue: 'oss',
+          required: true,
+          componentProps: {
+            options: [
+              { label: '阿里云', value: 'oss' },
+              { label: '亚马逊', value: 'aws' },
+            ],
+          },
+        },{
+          field: 'imprintCh',
+          component: 'ApiSelect',
+          required: true,
+          label: '代理商标识',
+          componentProps: {
+            api: async function (params) {
+              const res = (await AppFileAgentList(params)) as any as TreeNode[];
+              return res.map(ele => {
+                return {name:ele,value:ele}
+              });
+            },
+            numberToString: true,
+            labelField: 'name',
+            valueField: 'value',
+            immediate: true,
+            params: {
+              agentName: '',
+            },
+          },
+          colProps: {
+            span: 22,
+          },
+        },{
+          field: 'appType',
+          label: 'APP类型',
+          component: 'RadioButtonGroup',
+          defaultValue: 'Android',
+          required: true,
+          componentProps: {
+            options: [
+              { label: 'Android', value: 'Android' },
+              { label: 'IOS', value: 'IOS' },
+            ],
+          },
+        },
+        {
+          field: 'file',
+          component: 'Upload',
+          label: 'APP包',
+          required: true,
+          rules: [{ required: true, message: t('common.uploadMessge') }],
+          helpMessage: ['1、支持 .ipa、.apk文件,大小不超过1G','2、文件格式以4DKanKan+_版本号组成'],
+          itemProps: {
+            validateTrigger: 'onBlur',
+          },
+          componentProps: {
+            api: AppFileUpload,
+            maxNumber: 1,
+            maxSize: 1000,
+            accept: ['4dage','jpg','png'],
+            afterFetch: function (data) {
+              console.log('uploadApi',data)
+              const {fileName,md5} = data.data
+              uploadData.md5 = md5
+              uploadData.name = fileName
+              Reflect.set(data, 'url', data.data &&data.data.fileName);
+              return data;
+            },
+          },
+          colProps: {
+            span: 22,
+          },
+        },{
+          field: 'md5',
+          component: 'Input',
+          label: 'md5',
+          colProps: {
+            span: 22,
+          },
+          rules: [
+            {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              if (!value) {
+                return Promise.reject('请输入md5!');
+              }
+              console.log('value',value)
+              if (value !== uploadData.md5) {
+              //   /* eslint-disable-next-line */
+                return Promise.reject('MD5输入有误!');
+              }
+              return Promise.resolve();
+            },
+            trigger: 'change',
+          },
+          ],
+          required: true,
+        },
+        {
+          field: 'version0000',
+          component: 'Upload',
+          label: '版本信息',
+          required: true,
+          rules: [
+            {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              if (!value) {
+                /* eslint-disable-next-line */
+
+                return Promise.reject(t('common.uploadMessge'));
+              }
+              console.log('value',value)
+              if (value && value[0] !== 'version.json') {
+              //   /* eslint-disable-next-line */
+                return Promise.reject('仅能上传version.json文件');
+              }
+              return Promise.resolve();
+            },
+            trigger: 'change',
+          },
+          ],
+          helpMessage: ['仅能上传version.json文件,且不超过10 M'],
+          itemProps: {
+            validateTrigger: 'onBlur',
+          },
+          componentProps: {
+            api: AppFileUpload,
+            maxNumber: 1,
+            maxSize: 10,
+            accept: ['json'],
+            afterFetch: function (data) {
+              uploadData.versionName = data.data.fileName
+              Reflect.set(data, 'url', data.data &&data.data.fileName);
+              return data;
+            },
+          },
+          colProps: {
+            span: 22,
+          },
+        },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function renderOwnTypeLabel(type: number): string {
+        switch (type) {
+          case 2:
+            return t('routes.product.sdkType.2');
+          case 1:
+            return t('routes.product.sdkType.1');
+          case 3:
+            return t('routes.product.type.3');
+          default:
+            return '';
+        }
+      }
+      function rendercameraTypeLabel(cameraType: number): string {
+        switch (cameraType) {
+          case 4:
+            return t('routes.devices.cameraName.4');
+          case 1:
+            return t('routes.devices.cameraName.1');
+          case 9:
+            return t('routes.devices.cameraName.9');
+          case 10:
+            return t('routes.devices.cameraName.10');
+          case 6:
+            return t('routes.devices.cameraName.6');
+          default:
+            return '';
+        }
+      }
+      function onDataReceive(data) {
+        modelRef.value = data
+        resetFields();
+        setFieldsValue({
+          type:renderOwnTypeLabel(Number(data)),
+        });
+      }
+      const handleSubmit = async () => {
+        try {
+          const params = await validate();
+          if('version.json' != uploadData.versionName ){
+            return  createMessage.error('仅能上传version.json文件')
+          }
+          
+          if(params.md5 != uploadData.md5 ){
+            return  createMessage.error('您输入的MD5错误!')
+          }
+          const apiData = {
+            ...params as any,
+            md5:uploadData.md5,
+            name:uploadData.name,
+          }
+          console.log('res', apiData);
+          const res = await AppFileAdd(apiData);
+          console.log('res', res);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+        } catch (error) {
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        rendercameraTypeLabel,
+        renderOwnTypeLabel,
+        schemas,
+        registerForm,
+        model: modelRef,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        uploadData,
+        t,
+      };
+    },
+  });
+</script>

+ 59 - 50
src/views/product/app/index.vue

@@ -1,12 +1,10 @@
 <template>
     <BasicTable @register="registerTable">
       <template #toolbar>
-        <a-button type="primary" @click="exportExcel"> 导出</a-button>
+        <a-button type="primary" @click="()=>{openAddModal(true)}"> 新建</a-button>
       </template>
-      <template #bodyCell="{ column, record }">
-        <template v-if="column.key === 'action'">
+        <template #action="{ record }">
           <TableAction
-            stopButtonPropagation
             :actions="[
               {
                 label: '删除',
@@ -14,19 +12,10 @@
                 onClick: handleDelete.bind(null, record),
               },
             ]"
-            :dropDownActions="[
-              {
-                label: '启用',
-                popConfirm: {
-                  title: '是否启用?',
-                  confirm: handleOpen.bind(null, record),
-                },
-              },
-            ]"
           />
-        </template>
       </template>
     </BasicTable>
+    <AddModal @update="reload" @register="registerAddModal" />
 </template>
 <script lang="ts">
   import { defineComponent, h } from 'vue';
@@ -36,14 +25,16 @@
   import { Descriptions } from 'ant-design-vue';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useMessage } from '/@/hooks/web/useMessage';
-  import { Switch } from 'ant-design-vue';
-  import { InvoiceList,DownExport } from '/@/api/order'
+  import { AppFileList,AppFileDelete } from '/@/api/product'
+  import { useModal } from '/@/components/Modal';
+  import AddModal from './AddModal.vue';
 
   export default defineComponent({
     components: { 
       BasicTable, 
       TableAction, 
       PageWrapper,
+      AddModal,
       TableImg,
       [Descriptions.name]: Descriptions,
       [Descriptions.Item.name]: Descriptions.Item,
@@ -51,8 +42,35 @@
     setup() {
       const { t } = useI18n();
       const { createMessage,createConfirm } = useMessage();
+      const [registerAddModal, { openModal: openAddModal }] = useModal();
       const columns: BasicColumn[] = [
         {
+          title: '上传服务器',
+          dataIndex: 'fileServerType',
+          ellipsis: false,
+          width: 80,
+        },
+        {
+          title: '代理商标识',
+          dataIndex: 'agent',
+          width: 80,
+        },{
+          title: 'APP类型',
+          dataIndex: 'appType',
+          ellipsis: false,
+          width: 100,
+        },
+        {
+          title: 'APP包',
+          dataIndex: 'name',
+          width: 150,
+        },{
+          title: 'MD5',
+          dataIndex: 'md5',
+          ellipsis: false,
+          width: 250,
+        },
+        {
           title: '时间',
           dataIndex: 'createTime',
           width: 150,
@@ -66,34 +84,6 @@
             );
           },
         },
-        {
-          title: '订单号',
-          dataIndex: 'orderSn',
-          ellipsis: false,
-          width: 180,
-        },
-        {
-          title: '用户名',
-          dataIndex: 'userName',
-          width: 80,
-        },
-        {
-          title: '订单金额',
-          dataIndex: 'amount',
-          width: 80,
-        },
-        {
-          title: '支付方式',
-          dataIndex: 'payType',
-          // slots: { customRender: 'orderType' },
-          width: 80,
-        },
-        {
-          title: '订单状态',
-          dataIndex: 'payStatus',
-          // slots: { customRender: 'orderStatus' },
-          width: 80,
-        },
       ];
       const searchForm: Partial<FormProps> = {
         labelWidth: 100,
@@ -135,25 +125,41 @@
           }
         ],
       };
-      const [registerTable] = useTable({
-        api: InvoiceList,
-        title: '发票列表',
+      const [registerTable,{reload}] = useTable({
+        api: AppFileList,
+        title: 'App列表',
         // titleHelpMessage: ['已启用expandRowByClick', '已启用stopButtonPropagation'],
         columns: columns,
         useSearchForm: true,
         formConfig: searchForm,
         showTableSetting: true,
+        showIndexColumn:false,
         rowKey: 'id',
         fetchSetting: {
           pageField: 'pageNum',
-          sizeField: 'size',
-          listField: 'records',
+          sizeField: 'pageSize',
+          listField: 'list',
           totalField: 'total',
         },
+        actionColumn: {
+          width: 100,
+          title: '操作',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
         canResize: false,
       });
       function handleDelete(record: Recordable) {
-        console.log('点击了删除', record);
+        createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: () => h('span', '确定当删除当前记录?'),
+          onOk: async () => {
+            await AppFileDelete({ id: record.id });
+            createMessage.success(t('common.optSuccess'));
+            reload();
+          },
+        });
       }
       function handleOpen(record: Recordable) {
         console.log('点击了启用', record);
@@ -172,7 +178,10 @@
         registerTable,
         handleDelete,
         handleOpen,
+        registerAddModal,
+        openAddModal,
         exportExcel,
+        reload,
       };
     },
   });

+ 6 - 5
src/views/product/data.tsx

@@ -83,11 +83,12 @@ export const sdkTableSchema: BasicColumn[] = [
     width: 240,
     dataIndex: 'imprintCh',
   },
-  // {
-  //   title: '版本更新说明',
-  //   width: 150,
-  //   dataIndex: 'imprintEn',
-  // },
+  {
+    title: '版本更新说明',
+    width: 150,
+    ifShow:true,
+    dataIndex: 'status',
+  },
   {
     title: '首次发布时间',
     width: 120,

+ 11 - 4
src/views/product/sdk/index.vue

@@ -29,8 +29,9 @@
               },
               {
                 label: '下架',
+                ifShow:record.status == 1,
                 icon: 'akar-icons:statistic-down',
-                onClick: handleEdit.bind(null, record),
+                onClick: handleOff.bind(null, record),
               },
               {
                 label: '删除',
@@ -54,7 +55,7 @@ import { defineComponent, reactive, h } from 'vue';
 import { BasicTable, useTable, FormProps, TableAction, BasicColumn } from '/@/components/Table';
 import { PageWrapper } from '/@/components/Page';
 import { Divider, Card, Empty, Descriptions, Steps, Tabs } from 'ant-design-vue';
-import { SpaceSdkList, DelAndUpload, EditAndUpload } from '/@/api/product';
+import { SpaceSdkList, SpaceSdkDelete, SpaceSdkOnline } from '/@/api/product';
 import { sdkTableSchema } from '../data';
 import { useModal } from '/@/components/Modal';
 import { useI18n } from '/@/hooks/web/useI18n';
@@ -120,7 +121,7 @@ export default defineComponent({
       },
       searchInfo: searchInfo,
       actionColumn: {
-        width: 180,
+        width: 200,
         title: '操作',
         dataIndex: 'action',
         slots: { customRender: 'action' },
@@ -133,13 +134,18 @@ export default defineComponent({
     }
     async function handleDelete(record: Recordable) {
       console.log('点击了删除', record);
-      await DelAndUpload({ id: record.id });
+      await SpaceSdkDelete({ id: record.id });
       createMessage.success(t('common.optSuccess'));
       reload();
     }
     function handleOpen(record: Recordable) {
       console.log('点击了启用', record);
     }
+    async function handleOff(record: Recordable) {
+      await SpaceSdkOnline({ id: record.id, status:2 });//状态 0 - 未发布 1 -发布  2-下架
+      createMessage.success(t('common.optSuccess'));
+      reload();
+    }
     function handleEdit(record: Recordable) {
       console.log('record', record);
       openEditModal(true, {
@@ -151,6 +157,7 @@ export default defineComponent({
       handleDelete,
       handleOpen,
       tabChange,
+      handleOff,
       reload,
       registerAddModal,
       registerEditModal,

Diferenças do arquivo suprimidas por serem muito extensas
+ 9713 - 9823
yarn.lock