Kaynağa Gözat

feat: ✨ 完成销售管理-合同管理,封装上传组件

@sun-chaoqun 2 yıl önce
ebeveyn
işleme
e623590ee7

+ 1 - 1
src/api/index.ts

@@ -12,7 +12,7 @@ let loadingInstance: LoadingType = {}
 
 const config = {
   // 默认地址请求地址,可在 .env.*** 文件中修改
-  // baseURL: import.meta.env.VITE_BZD_ERP_APP_API as string,
+  baseURL: import.meta.env.VITE_BZD_ERP_APP_API as string,
   // 设置超时时间(10s)
   timeout: ResultEnum.TIMEOUT as number,
   // 跨域时候允许携带凭证

+ 18 - 0
src/api/storehouse/index.ts

@@ -53,3 +53,21 @@ export const Storehouse_IotCard_Add = (params: any) => $http.post('/testapi/stor
 export const Storehouse_IotCard_Edit = (params: any) => $http.post('/testapi/storage/IotCard/Edit', params)
 // 物联网卡删除
 export const Storehouse_IotCard_Del = (params: any) => $http.post('/testapi/storage/IotCard/Del', params)
+
+/**
+ * 销售管理 -> 合同
+ */
+// 合同列表
+export const Storehouse_Contract_List = (params: any) => $http.post('/testapi/storage/Contract/List', params)
+// 合同-销售
+export const Storehouse_Contract_User_List = (params: any) => $http.post('/testapi/storage/Contract/User_List', params)
+// 详情
+export const Storehouse_Contract_Get = (params: any) => $http.post('/testapi/storage/Contract/Get', params)
+// 审批
+export const Storehouse_Contract_Approval = (params: any) => $http.post('/testapi/storage/Contract/Approval', params)
+// 添加
+export const Storehouse_Contract_Add = (params: any) => $http.post('/testapi/storage/Contract/Add', params)
+// 修改
+export const Storehouse_Contract_Edit = (params: any) => $http.post('/testapi/storage/Contract/Edit', params)
+// 删除
+export const Storehouse_Contract_Del = (params: any) => $http.post('/testapi/storage/Contract/Del', params)

+ 1 - 1
src/components/TableBase/index.vue

@@ -51,7 +51,7 @@ const resize = () => {
   } else {
     const height = document.documentElement.clientHeight
     const header = document.querySelector('.table-header') as HTMLDivElement
-    cardHeight = height - header.clientHeight - 12 - 60 - 12
+    cardHeight = height - header.clientHeight - 12 - 60 - 12 - 12
   }
 }
 onMounted(() => {

+ 21 - 11
src/components/Upload/index.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { ref, watch } from 'vue'
-import { Plus } from '@element-plus/icons-vue'
+// import { Plus } from '@element-plus/icons-vue'
 import { UpFileToken } from '@/api/public/index'
 import { ElMessage, genFileId, ElLoading } from 'element-plus'
 import { GlobalStore } from '@/stores/index'
@@ -15,7 +15,9 @@ interface ProTableProps {
   isImg?: boolean // 是否只能上传图片
   disabled?: boolean
   limit: number
+  accept: string
   modelValue: string | string[]
+  listType?: string
 }
 
 const upload = ref()
@@ -36,11 +38,11 @@ const handlePictureCardPreview = async (file: any) => {
 }
 // 图片上传之前
 const beforeUpload = async (file: any) => {
-  let reg = /^image/g
-  if (isImg.value && !reg.test(file.type)) {
-    ElMessage.error('必须上传图片!!')
-    return
-  }
+  // let reg = /^image/g
+  // if (isImg.value && !reg.test(file.type)) {
+  //   ElMessage.error('必须上传图片!!')
+  //   return
+  // }
   loadingInstance = ElLoading.service({
     lock: true,
     text: 'Loading',
@@ -93,12 +95,12 @@ const handleRemove: UploadProps['onRemove'] = (uploadFile: any, uploadFiles: Upl
 
 // 接受父组件参数,配置默认值
 const props = withDefaults(defineProps<ProTableProps>(), {
-  disabled: false
-  // modelValue: ''
+  disabled: false,
+  listType: 'picture-card'
 })
 
 const fileList = ref<FileListType[]>([])
-const isImg = ref<boolean>(props.isImg)
+// const isImg = ref<boolean>(props.isImg)
 const limit = ref<number>(props.limit)
 
 watch(
@@ -134,10 +136,11 @@ defineExpose({
   <div>
     <el-upload
       ref="upload"
+      :accept="accept"
       :disabled="disabled"
       v-model:file-list="fileList"
       action="https://up-z2.qiniup.com"
-      list-type="picture-card"
+      :list-type="listType"
       :limit="limit"
       :auto-upload="true"
       :before-upload="beforeUpload"
@@ -148,7 +151,14 @@ defineExpose({
       :on-preview="handlePictureCardPreview"
       :data="uploadData"
     >
-      <el-icon><Plus /></el-icon>
+      <slot></slot>
+      <!-- <el-button type="primary">Click to upload</el-button>
+      <el-icon><Plus /></el-icon> -->
+      <template #tip>
+        <div class="el-upload__tip">
+          <slot name="tip"></slot>
+        </div>
+      </template>
     </el-upload>
     <el-dialog v-model="dialogVisible">
       <img w-full :src="dialogImageUrl" class="full-img" alt="Preview Image" />

+ 1 - 1
src/components/dialog/Dialog.vue

@@ -39,7 +39,7 @@ defineExpose({
 </template>
 
 <style scoped>
-:deep(.el-dialog){
+:deep(.el-dialog) {
   animation: anim-open 1s linear !important;
 }
 .el-dialog__wrapper {

+ 1 - 1
src/layouts/Header/Breadcrumb.vue

@@ -10,7 +10,7 @@ const router = useRouter()
 const breadcrumbList: any = computed(() => {
   return route.matched
 })
-
+console.log(breadcrumbList)
 const isUnicode = (val: string | undefined) => {
   if (!val) return false
   return !/ue/g.test(val)

+ 40 - 32
src/router/modules/staticRouter.ts

@@ -13,38 +13,46 @@ export const staticRouter: RouteRecordRaw[] = [
       title: '起始页'
     },
     children: [
-      {
-        path: '/list',
-        name: 'List',
-        component: () => import('@/views/storehouse/List.vue'),
-        meta: {
-          title: '仓库列表'
-        }
-      },
-      {
-        path: '/classify',
-        name: 'Classify',
-        component: () => import('@/views/storehouse/Classify.vue'),
-        meta: {
-          title: '产品分类'
-        }
-      },
-      {
-        path: '/productionList',
-        name: 'ProductionList',
-        component: () => import('@/views/storehouse/ProductionList.vue'),
-        meta: {
-          title: '产品列表'
-        }
-      },
-      {
-        path: '/ioTNetworkCard',
-        name: 'IoTNetworkCard',
-        component: () => import('@/views/storehouse/IoTNetworkCard.vue'),
-        meta: {
-          title: '物联网卡'
-        }
-      }
+      // {
+      //   path: '/list',
+      //   name: 'List',
+      //   component: () => import('@/views/storehouse/List.vue'),
+      //   meta: {
+      //     title: '仓库列表'
+      //   }
+      // },
+      // {
+      //   path: '/classify',
+      //   name: 'Classify',
+      //   component: () => import('@/views/storehouse/Classify.vue'),
+      //   meta: {
+      //     title: '产品分类'
+      //   }
+      // },
+      // {
+      //   path: '/productionList',
+      //   name: 'ProductionList',
+      //   component: () => import('@/views/storehouse/ProductionList.vue'),
+      //   meta: {
+      //     title: '产品列表'
+      //   }
+      // },
+      // {
+      //   path: '/ioTNetworkCard',
+      //   name: 'IoTNetworkCard',
+      //   component: () => import('@/views/storehouse/IoTNetworkCard.vue'),
+      //   meta: {
+      //     title: '物联网卡'
+      //   }
+      // },
+      // {
+      //   path: '/contract',
+      //   name: 'Contract',
+      //   component: () => import('@/views/storehouse/sales/Contract.vue'),
+      //   meta: {
+      //     title: '合同管理'
+      //   }
+      // }
     ]
   },
   {

+ 11 - 11
src/stores/index.ts

@@ -3,8 +3,8 @@ import { GlobalState } from './interface/index'
 import { Menu_User_List } from '@/api/role/index'
 import { User_Info } from '@/api/user/index'
 import { isEmptyObject } from '@/utils/common'
-import { User_Dept_List, User_Post_List } from '@/api/user/index'
-import { flatMenuListGet, AddRouterMeta } from '@/utils/common'
+import { User_Dept_List } from '@/api/user/index'
+import { flatMenuListGet, AddRouterMeta, getAllBreadcrumbList } from '@/utils/common'
 
 export const GlobalStore = defineStore({
   id: 'GlobalState',
@@ -17,9 +17,10 @@ export const GlobalStore = defineStore({
     UserPostList: [],
     User_tokey: localStorage.getItem('User_tokey') || '',
     path: '',
-    MenuList: [],
-    allBreadcrumbList: [],
-    flatMenu: []
+    MenuList: [], // 保存原始菜单
+    allBreadcrumbList: [], // 用作面包屑
+    expandMenuList: [], // 扩展菜单属性
+    flatMenu: [] // 扁平化的数组
   }),
   getters: {
     GET_User_tokey: state => state.User_tokey,
@@ -58,10 +59,6 @@ export const GlobalStore = defineStore({
       const res: any = await User_Dept_List()
       this.UserDeptList = res.Data
     },
-    async SET_User_Post_List() {
-      const res = await User_Post_List()
-      console.log(res)
-    },
     async SET_UserInfo() {
       // 如果以及有用户数据,则不请求
       if (isEmptyObject(this.GET_User_Info)) return
@@ -75,8 +72,11 @@ export const GlobalStore = defineStore({
       const res: any = await Menu_User_List({ User_tokey: this.GET_User_tokey })
       if (res.Code === 200) {
         this.MenuList = res.Data.Data // 保存所有菜单
-        this.allBreadcrumbList = AddRouterMeta(res.Data.Data) // 扩展菜单属性
-        this.SET_Flat_Menu(flatMenuListGet(this.allBreadcrumbList)) // 接收扁平化菜单
+        this.expandMenuList = AddRouterMeta(res.Data.Data) // 扩展菜单属性
+
+        this.SET_Flat_Menu(flatMenuListGet(this.expandMenuList)) // 接收扁平化菜单
+
+        this.allBreadcrumbList = getAllBreadcrumbList(this.expandMenuList)
       }
     }
   }

+ 1 - 0
src/stores/interface/index.ts

@@ -9,6 +9,7 @@ export interface GlobalState {
   UserPostList: string[]
   UserDeptList: UserDeptType[]
   MenuList: Menu.MenuOptions[]
+  expandMenuList: any
   path: string
   allBreadcrumbList: any
   flatMenu: any

+ 16 - 0
src/style.scss

@@ -19,12 +19,16 @@ body,
   height: 100%;
   width: 100%;
 }
+html {
+  font-size: 16px; /*设置html字体大小为16px*/
+}
 body {
   line-height: 1.5em;
   font-family: 'Trebuchet MS', Arial, Helvetica, sans-serif !important;
   line-height: 25px;
   letter-spacing: 1px;
   font-size: 14px;
+  // font-size: 1rem;
   color: #4e4e4e;
 }
 
@@ -122,4 +126,16 @@ body {
   }
 }
 
+@media only screen and (max-width: 600px) {
+  body {
+    font-size: 14px;
+  }
+}
+
+@media only screen and (min-width: 600px) and (max-width: 1200px) {
+  body {
+    font-size: 16px;
+  }
+}
+
 @import '@/assets/iconfont.css';

+ 1 - 1
src/views/Index.vue

@@ -8,7 +8,7 @@ import { useRoute } from 'vue-router'
 
 const globalStore = GlobalStore()
 
-const routerList = computed(() => globalStore.allBreadcrumbList)
+const routerList = computed(() => globalStore.expandMenuList)
 
 onMounted(() => {
   globalStore.SET_User_Dept_List()

+ 6 - 6
src/views/account/users/components/relus.ts

@@ -44,15 +44,15 @@ export const reuls_validator: FormRules = {
   T_id_card: [{ required: true, validator: validate_T_id_card, trigger: 'blur' }],
   T_nation: [{ required: true, message: '请输入民族', trigger: 'blur' }],
   T_school: [{ required: true, message: '请输入毕业院校', trigger: 'blur' }],
-  T_major: [{ required: true, message: '请输入专业', trigger: 'blur' }],
-  T_education: [{ required: true, message: '请输入学历', trigger: 'blur' }],
-  T_marry: [{ required: true, message: '请选择婚否', trigger: 'change' }],
+  // T_major: [{ required: true, message: '请输入专业', trigger: 'blur' }],
+  // T_education: [{ required: true, message: '请输入学历', trigger: 'blur' }],
+  // T_marry: [{ required: true, message: '请选择婚否', trigger: 'change' }],
   T_phone: [{ required: true, validator: validate_T_phone, trigger: 'blur' }],
   T_entry_time: [{ required: true, message: '请选择入职时间', trigger: 'blur' }],
-  T_positive_time: [{ required: true, message: '请选择转正时间', trigger: 'blur' }],
+  // T_positive_time: [{ required: true, message: '请选择转正时间', trigger: 'blur' }],
   T_entry_type: [{ required: true, message: '请选择入职类型', trigger: 'change' }],
-  T_contract_start_time: [{ required: true, message: '请选择开始时间', trigger: 'blur' }],
-  T_contract_end_time: [{ required: true, message: '请选择结束时间', trigger: 'blur' }],
+  // T_contract_start_time: [{ required: true, message: '请选择开始时间', trigger: 'blur' }],
+  // T_contract_end_time: [{ required: true, message: '请选择结束时间', trigger: 'blur' }],
   T_expire: [{ required: true, message: '请选择是否到期', trigger: 'change' }],
   T_spouse_phone: [{ required: true, validator: validate_T_spouse_phone, trigger: 'blur' }],
   T_dept_leader: [{ required: true, message: '是否为部门负责人', trigger: 'change' }]

+ 2 - 2
src/views/storehouse/IoTNetworkCard.vue

@@ -23,7 +23,7 @@ const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
 
 const columns: ColumnProps[] = [
   { type: 'index', label: '序号', width: 80 },
-  { prop: 'T_iccid', label: '物联网卡号' },
+  { prop: 'T_iccid', label: '物联网卡号', ellipsis: true },
   { prop: 'T_sn', label: '关联SN' },
   { prop: 'T_State', label: '状态', name: 'T_State' },
   { prop: 'T_type', label: '类型' },
@@ -197,7 +197,7 @@ const searchHandle = () => {
       </template>
       <el-form ref="ruleFormRef" :model="form" :rules="rules">
         <el-divider border-style="dashed" />
-        <el-form-item label="物联网卡号:" :label-width="formLabelWidth" prop="T_number">
+        <el-form-item label="物联网卡号:" :label-width="formLabelWidth" prop="T_iccid">
           <el-input v-model="form.T_iccid" type="text" autocomplete="off" placeholder="请输入物联网卡号" />
         </el-form-item>
         <el-form-item label="型号:" :label-width="formLabelWidth" prop="T_type">

+ 3 - 3
src/views/storehouse/ProductionList.vue

@@ -13,10 +13,10 @@ import Upload from '@/components/Upload/index.vue'
 import Drawer from '@/components/Drawer/index.vue'
 import Dialog from '@/components/dialog/Dialog.vue'
 import TableBase from '@/components/TableBase/index.vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { Edit, Delete, Picture } from '@element-plus/icons-vue'
 import type { ColumnProps } from '@/components/TableBase/interface/index'
-import { ElNotification, ElMessageBox, ElMessage } from 'element-plus'
 
 const isNew = ref(true)
 const globalStore = GlobalStore()
@@ -30,7 +30,7 @@ const columns: ColumnProps[] = [
   { prop: 'T_img', label: '产品图片', name: 'T_img' },
   { prop: 'T_name', label: '产品名称' },
   { prop: 'T_class_name', label: '产品分类' },
-  { prop: 'T_model', label: '产品型号' },
+  { prop: 'T_model', label: '产品型号', ellipsis: true },
   { prop: 'T_spec', label: '产品规格' },
   { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
   { prop: 'T_name', label: '更新时间' },
@@ -253,7 +253,7 @@ onMounted(() => {
         </el-form-item>
         <el-form-item label="产品图片:" class="m-b-6" :label-width="formLabelWidth" prop="T_img">
           <!-- <Upload ref="uploadRef" v-if="isNew || form.T_img" :isImg="true" :limit="1" v-model="form.T_img"></Upload> -->
-          <Upload ref="uploadRef" :isImg="true" :limit="1" v-model="form.T_img"></Upload>
+          <Upload ref="uploadRef" :isImg="true" :limit="1" v-model="form.T_img" accept="image/*"></Upload>
         </el-form-item>
         <el-form-item label="备注:" class="m-b-6" :label-width="formLabelWidth" prop="T_remark">
           <el-input

+ 244 - 0
src/views/storehouse/sales/Agreement.vue

@@ -0,0 +1,244 @@
+<script setup lang="ts">
+import {
+  Storehouse_IotCard_List,
+  Storehouse_IotCard_Add,
+  Storehouse_IotCard_Edit,
+  Storehouse_IotCard_Del
+} from '@/api/storehouse/index'
+import { GlobalStore } from '@/stores/index'
+import { ref, reactive, nextTick } from 'vue'
+import Drawer from '@/components/Drawer/index.vue'
+import TableBase from '@/components/TableBase/index.vue'
+import type { FormInstance, FormRules } from 'element-plus'
+import { Edit, Delete } from '@element-plus/icons-vue'
+import type { ColumnProps } from '@/components/TableBase/interface/index'
+import { ElMessageBox, ElMessage } from 'element-plus'
+
+const isNew = ref(true)
+const globalStore = GlobalStore()
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+
+const columns: ColumnProps[] = [
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_iccid', label: '物联网卡号' },
+  { prop: 'T_sn', label: '关联SN' },
+  { prop: 'T_State', label: '状态', name: 'T_State' },
+  { prop: 'T_type', label: '类型' },
+  { prop: 'operation', label: '操作', width: 200, fixed: 'right' }
+]
+
+// 添加仓库名称
+type Fn = () => void
+const form = reactive({
+  T_id: '',
+  T_sn: '',
+  T_type: '',
+  T_state: null,
+  T_iccid: ''
+})
+
+const validate_T_sn = (rule: any, value: any, callback: any) => {
+  if (form.T_state === 2 && value === '') {
+    callback(new Error('请输入关联SN'))
+  } else {
+    callback()
+  }
+}
+
+const rules = reactive<FormRules>({
+  T_iccid: [{ required: true, message: '请输入物联网卡号', trigger: 'blur' }],
+  T_state: [{ required: true, message: '请选择状态', trigger: 'blur' }],
+  T_type: [{ required: true, message: '请输入型号', trigger: 'blur' }],
+  T_sn: [{ validator: validate_T_sn, trigger: 'blur' }]
+})
+
+const callbackDrawer = (done: Fn) => {
+  resetForm(ruleFormRef.value)
+  done()
+}
+const openDrawer = (type: string, row?: any) => {
+  isNew.value = type === 'new' ? true : false
+  nextTick(() => {
+    form.T_id = row.Id
+    form.T_sn = row.T_sn
+    form.T_type = row.T_type
+    form.T_state = row.T_State
+    form.T_iccid = row.T_iccid
+  })
+  drawerRef.value?.openDrawer()
+}
+
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+}
+
+const AddUserName = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async valid => {
+    if (valid) {
+      let res: any = {}
+      if (isNew.value) {
+        console.log(form)
+        res = await Storehouse_IotCard_Add({ User_tokey: globalStore.GET_User_tokey, ...form })
+      } else {
+        res = await Storehouse_IotCard_Edit({
+          User_tokey: globalStore.GET_User_tokey,
+          ...form
+        })
+      }
+      if (res.Code === 200) {
+        ElMessage.success(`${isNew.value ? '添加' : '修改'}物联网卡成功!!`)
+        nextTick(() => {
+          drawerRef.value?.closeDrawer()
+          TableRef.value?.getTableList()
+          resetForm(ruleFormRef.value)
+          isNew.value = true
+        })
+      }
+    } else {
+      return false
+    }
+  })
+}
+
+// 删除
+const UserDelete = (row: any) => {
+  ElMessageBox.confirm('您确定要删除该物联网卡号吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res: any = await Storehouse_IotCard_Del({ User_tokey: globalStore.GET_User_tokey, T_id: row.Id })
+      if (res.Code === 200) {
+        ElMessage.success('删除成功!')
+        nextTick(() => {
+          TableRef.value?.getTableList()
+        })
+      }
+    })
+    .catch(() => {
+      ElMessage.warning('取消成功!')
+    })
+}
+
+// 搜索
+const options = reactive([
+  { name: '未使用', id: 1 },
+  { name: '已使用', id: 2 },
+  { name: '已作废', id: 3 }
+])
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: '',
+  T_state: ''
+})
+const searchHandle = () => {
+  TableRef.value?.searchTable()
+}
+</script>
+
+<template>
+  <div class="agreement">
+    <TableBase ref="TableRef" :columns="columns" :requestApi="Storehouse_IotCard_List" :initParam="initParam">
+      <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+            <el-col :xl="6" :lg="8" :md="10">
+              <span class="inline-flex items-center">关键词:</span>
+              <el-input
+                v-model="initParam.T_name"
+                type="text"
+                class="w-50 m-2"
+                placeholder="按物联网卡号或SN搜索"
+                @change="searchHandle"
+              />
+            </el-col>
+            <el-col :xl="10" :md="12">
+              <span class="inline-flex items-center">产品分类:</span>
+              <el-select v-model="initParam.T_state" class="w-50 m-2" clearable placeholder="请选择状态~">
+                <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+              <el-button type="primary" @click="searchHandle">搜索</el-button>
+            </el-col>
+            <el-col :xl="6" :md="2" class="btn"
+              ><el-button type="primary" @click="openDrawer('new')">添加</el-button></el-col
+            >
+          </el-row>
+        </div>
+      </template>
+      <template #T_State="{ row }">
+        <el-tag v-if="row.T_State === 2" type="success" effect="dark"> 已使用 </el-tag>
+        <el-tag v-else-if="row.T_State === 1" type="warning" effect="dark"> 未使用 </el-tag>
+        <el-tag v-else type="info" effect="dark"> 已作废 </el-tag>
+      </template>
+      <template #right="{ row }">
+        <el-button
+          :disabled="row.T_State === 2"
+          link
+          type="primary"
+          size="small"
+          :icon="Edit"
+          @click="openDrawer('edit', row)"
+          >编辑</el-button
+        >
+        <el-button :disabled="row.T_State === 2" link type="danger" size="small" :icon="Delete" @click="UserDelete(row)"
+          >删除</el-button
+        >
+      </template>
+    </TableBase>
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">{{ isNew ? '添加' : '编辑' }} - 物联网卡</h4>
+      </template>
+      <el-form ref="ruleFormRef" :model="form" :rules="rules">
+        <el-divider border-style="dashed" />
+        <el-form-item label="物联网卡号:" :label-width="formLabelWidth" prop="T_iccid">
+          <el-input v-model="form.T_iccid" type="text" autocomplete="off" placeholder="请输入物联网卡号" />
+        </el-form-item>
+        <el-form-item label="型号:" :label-width="formLabelWidth" prop="T_type">
+          <el-input v-model="form.T_type" type="text" autocomplete="off" placeholder="请输入型号" />
+        </el-form-item>
+        <el-form-item label="状态:" :label-width="formLabelWidth" prop="T_state">
+          <el-radio-group v-model="form.T_state">
+            <el-radio :label="2">已使用</el-radio>
+            <el-radio :label="1">未使用</el-radio>
+            <el-radio :label="3">已作废</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="关联SN:" :label-width="formLabelWidth" prop="T_sn">
+          <el-input v-model="form.T_sn" type="text" autocomplete="off" placeholder="请输入关联SN" />
+        </el-form-item>
+        <el-form-item :label-width="formLabelWidth">
+          <el-button v-if="isNew" color="#626aef" @click="AddUserName(ruleFormRef)">提交</el-button>
+          <el-button v-else color="#626aef" @click="AddUserName(ruleFormRef)">修改</el-button>
+        </el-form-item>
+      </el-form>
+    </Drawer>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.IoTNetworkCard {
+  :deep(.el-drawer__header) {
+    margin-bottom: 0;
+  }
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .btn {
+      display: flex;
+      justify-content: end;
+    }
+    .w-50 {
+      width: 12.5rem;
+    }
+  }
+}
+</style>

+ 136 - 0
src/views/storehouse/sales/Contract.vue

@@ -0,0 +1,136 @@
+<script setup lang="ts">
+import { Storehouse_Contract_List, Storehouse_IotCard_Del } from '@/api/storehouse/index'
+import { GlobalStore } from '@/stores/index'
+import { ref, reactive, nextTick } from 'vue'
+import ContractForm from './ContractForm.vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import TableBase from '@/components/TableBase/index.vue'
+import { Edit, Delete, Finished, View } from '@element-plus/icons-vue'
+import type { ColumnProps } from '@/components/TableBase/interface/index'
+
+const globalStore = GlobalStore()
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const ContractFormRef = ref<InstanceType<typeof ContractForm> | null>(null)
+
+const columns: ColumnProps[] = [
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_number', label: '合同编号' },
+  { prop: 'T_customer', label: '客户名称' },
+  { prop: 'T_State', label: '状态', name: 'T_State' },
+  { prop: 'T_out', label: '是否出库' },
+  { prop: 'T_type', label: '更新时间' },
+  { prop: 'operation', label: '操作', width: 260, fixed: 'right' }
+]
+
+const openContractFormDrawer = (type: string, row?: any) => {
+  ContractFormRef.value?.openDrawer(type, row)
+}
+
+// 删除
+const UserDelete = (row: any) => {
+  ElMessageBox.confirm('您确定要删除该物联网卡号吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res: any = await Storehouse_IotCard_Del({ User_tokey: globalStore.GET_User_tokey, T_id: row.Id })
+      if (res.Code === 200) {
+        ElMessage.success('删除成功!')
+        nextTick(() => {
+          TableRef.value?.getTableList()
+        })
+      }
+    })
+    .catch(() => {
+      ElMessage.warning('取消成功!')
+    })
+}
+
+// 搜索
+const options = reactive([
+  { name: '已通过', id: 1 },
+  { name: '未通过', id: 2 },
+  { name: '待审核', id: 3 }
+])
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: '',
+  T_state: ''
+})
+const searchHandle = () => {
+  TableRef.value?.searchTable()
+}
+</script>
+
+<template>
+  <div class="contract">
+    <TableBase ref="TableRef" :columns="columns" :requestApi="Storehouse_Contract_List" :initParam="initParam">
+      <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+            <el-col :xl="6" :lg="8" :md="10">
+              <span class="inline-flex items-center">合同编号:</span>
+              <el-input
+                v-model="initParam.T_name"
+                type="text"
+                class="w-50 m-2"
+                placeholder="按产品名称搜索"
+                @change="searchHandle"
+              />
+            </el-col>
+            <el-col :xl="10" :md="12">
+              <span class="inline-flex items-center">状态:</span>
+              <el-select v-model="initParam.T_state" class="w-50" clearable placeholder="请选择状态~">
+                <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+              <el-button type="primary" @click="searchHandle">搜索</el-button>
+            </el-col>
+            <el-col :xl="6" :md="2" class="btn"
+              ><el-button type="primary" @click="openContractFormDrawer('new')">添加</el-button></el-col
+            >
+          </el-row>
+        </div>
+      </template>
+      <template #T_State="{ row }">
+        <el-tag v-if="row.T_State === 2" type="success" effect="dark"> 已使用 </el-tag>
+        <el-tag v-else-if="row.T_State === 1" type="warning" effect="dark"> 未使用 </el-tag>
+        <el-tag v-else type="info" effect="dark"> 已作废 </el-tag>
+      </template>
+      <template #right="{ row }">
+        <el-button :disabled="row.T_State === 2" link type="warning" size="small" :icon="Finished">审核</el-button>
+        <el-button :disabled="row.T_State === 2" link type="primary" size="small" :icon="Edit" @click="UserDelete(row)"
+          >编辑</el-button
+        >
+        <el-button :disabled="row.T_State === 2" link type="success" size="small" :icon="View" @click="UserDelete(row)"
+          >详情</el-button
+        >
+        <el-button :disabled="row.T_State === 2" link type="danger" size="small" :icon="Delete" @click="UserDelete(row)"
+          >删除</el-button
+        >
+      </template>
+    </TableBase>
+    <ContractForm ref="ContractFormRef" />
+  </div>
+</template>
+
+<style scoped lang="scss">
+.contract {
+  :deep(.el-drawer__header) {
+    margin-bottom: 0;
+  }
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .btn {
+      display: flex;
+      justify-content: end;
+    }
+    .w-50 {
+      width: 12.5rem;
+    }
+  }
+}
+</style>

+ 339 - 0
src/views/storehouse/sales/ContractForm.vue

@@ -0,0 +1,339 @@
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import Drawer from '@/components/Drawer/index.vue'
+import Dialog from '@/components/dialog/Dialog.vue'
+import type { FormInstance, FormRules } from 'element-plus'
+// import type { TableColumnCtx } from 'element-plus'
+import { Delete } from '@element-plus/icons-vue'
+import { GlobalStore } from '@/stores/index'
+import TableBase from '@/components/TableBase/index.vue'
+import Upload from '@/components/Upload/index.vue'
+import { Storehouse_Product_List } from '@/api/storehouse/index'
+// import { Storehouse_IotCard_Add, Storehouse_IotCard_Edit } from '@/api/storehouse/index'
+
+const isNew = ref(true)
+const globalStore = GlobalStore()
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const dialogRef = ref<InstanceType<typeof Dialog> | null>(null)
+const uploadRef = ref<InstanceType<typeof Upload> | null>(null)
+const TableProductRef = ref<InstanceType<typeof TableBase> | null>(null)
+
+const validate_T_product = (rule: any, value: any, callback: any) => {
+  if (form.T_type === 1 && value === '') {
+    callback(new Error('请选择产品明细'))
+  } else {
+    callback()
+  }
+}
+
+const rules = reactive<FormRules>({
+  T_number: [{ required: true, message: '请输入物联网卡号', trigger: 'blur' }],
+  T_customer: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
+  T_type: [{ required: true, message: '请选择合同类型', trigger: 'blur' }],
+  T_product: [{ validator: validate_T_product, trigger: 'blur' }],
+  T_money: [{ required: true, message: '请输入合同金额', trigger: 'blur' }],
+  T_date: [{ required: true, message: '请选择业务日期', trigger: 'blur' }]
+})
+
+const callbackDrawer = (done: Fn) => {
+  resetForm(ruleFormRef.value)
+  done()
+}
+
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+}
+
+// 添加仓库名称
+type Fn = () => void
+const form = reactive({
+  T_id: '',
+  T_number: '',
+  T_customer: '',
+  T_type: null,
+  T_product: '',
+  T_money: '',
+  T_date: '',
+  T_remark: '',
+  T_pdf: ''
+})
+
+const openDrawer = (type: string, row?: any) => {
+  console.log(type, row)
+  isNew.value = type === 'new' ? true : false
+  // nextTick(() => {
+  //   form.T_id = row.Id
+  //   form.T_sn = row.T_sn
+  //   form.T_type = row.T_type
+  //   form.T_state = row.T_State
+  //   form.T_iccid = row.T_iccid
+  // })
+  drawerRef.value?.openDrawer()
+}
+
+// const AddUserName = (formEl: FormInstance | undefined) => {
+//   if (!formEl) return
+//   formEl.validate(async valid => {
+//     if (valid) {
+//       let res: any = {}
+//       if (isNew.value) {
+//         console.log(form)
+//         res = await Storehouse_IotCard_Add({ User_tokey: globalStore.GET_User_tokey, ...form })
+//       } else {
+//         res = await Storehouse_IotCard_Edit({
+//           User_tokey: globalStore.GET_User_tokey,
+//           ...form
+//         })
+//       }
+//       if (res.Code === 200) {
+//         ElMessage.success(`${isNew.value ? '添加' : '修改'}物联网卡成功!!`)
+//         nextTick(() => {
+//           drawerRef.value?.closeDrawer()
+//           TableRef.value?.getTableList()
+//           resetForm(ruleFormRef.value)
+//           isNew.value = true
+//         })
+//       }
+//     } else {
+//       return false
+//     }
+//   })
+// }
+
+// const AddUserName = () => {
+//   console.log(tableData)
+// }
+// 增加产品
+// dialog
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: '',
+  T_model: '',
+  T_class: ''
+})
+const classOptions = reactive<any[]>([])
+const modelOptions = reactive<any[]>([])
+const AddProductionDetailed = () => {
+  dialogRef.value?.DialogOpen()
+}
+const searchHandle = () => {
+  //
+}
+
+const tableData = reactive<any[]>([])
+const columns = [
+  { type: 'index', label: '序号', width: 80, align: 'center ' },
+  { label: '产品图片', prop: 'T_img', align: 'center ' },
+  { label: '产品名称', prop: 'T_name', align: 'center ' },
+  { label: '产品分类', prop: 'T_class_name', align: 'center ' },
+  { label: '产品型号', prop: 'T_model', align: 'center ' },
+  { label: '产品规格', prop: 'T_spec', align: 'center ' },
+  { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120 },
+  { label: '*数量', prop: 'count', align: 'center ', name: 'count' },
+  { label: '备注', prop: 'id', align: 'center ' },
+  { prop: 'operation', label: '操作', width: 80, fixed: 'right' }
+]
+
+const productColumns = [
+  { type: 'selection', width: 80, name: 'selection' },
+  { prop: 'T_img', label: '产品图片', name: 'T_img' },
+  { prop: 'T_name', label: '产品名称' },
+  { prop: 'T_class_name', label: '产品分类' },
+  { prop: 'T_model', label: '产品型号' },
+  { prop: 'T_spec', label: '产品规格' },
+  { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
+  { prop: 'T_name', label: '更新时间' }
+]
+
+defineExpose({
+  openDrawer
+})
+</script>
+
+<template>
+  <div class="contract-form">
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer" size="80%">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">{{ isNew ? '添加' : '编辑' }} - 合同</h4>
+      </template>
+      <el-form ref="ruleFormRef" :model="form" :rules="rules">
+        <el-divider border-style="dashed" />
+        <el-form-item label="合同编号:" :label-width="formLabelWidth" prop="T_number">
+          <el-input
+            v-model="form.T_number"
+            type="text"
+            autocomplete="off"
+            placeholder="请输入物联网卡号"
+            class="w-50"
+          />
+        </el-form-item>
+        <el-form-item label="客户名称:" :label-width="formLabelWidth" prop="T_customer">
+          <el-input v-model="form.T_customer" type="text" autocomplete="off" placeholder="请输入型号" class="w-50" />
+        </el-form-item>
+        <el-form-item label="合同类型:" :label-width="formLabelWidth" prop="T_type">
+          <el-select v-model="form.T_type" class="w-50" clearable placeholder="请选择~">
+            <el-option label="销售合同" :value="1" />
+            <el-option label="验证合同" :value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="产品明细:" :label-width="formLabelWidth" prop="T_product">
+          <el-table
+            :data="tableData"
+            style="width: 100%"
+            border
+            stripe
+            :header-cell-style="{
+              background: '#909399',
+              height: '50px',
+              color: '#fff'
+            }"
+          >
+            <template v-for="item in columns" :key="item.prop">
+              <el-table-column v-bind="item" v-if="item.fixed !== 'right'">
+                <template #header v-if="item.prop === 'count'">
+                  <span style="color: red">*数量</span>
+                </template>
+                <template #default="{ row }" v-if="item.prop === item.name && item.prop">
+                  <el-input v-model.number="row.count" type="text" autocomplete="off" />
+                </template>
+              </el-table-column>
+              <el-table-column v-bind="item" v-if="item.fixed === 'right'">
+                <el-button link type="danger" size="small" :icon="Delete">删除</el-button>
+              </el-table-column>
+            </template>
+            <template #append>
+              <el-button type="primary" @click="AddProductionDetailed">
+                <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加产品</span>
+              </el-button>
+            </template>
+          </el-table>
+        </el-form-item>
+        <el-form-item label="业务日期:" :label-width="formLabelWidth" prop="T_date">
+          <el-date-picker
+            class="my-date-picker"
+            style="width: 21.5rem"
+            v-model="form.T_date"
+            type="date"
+            placeholder="选择日期"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item label="合同金额:" :label-width="formLabelWidth" prop="T_money">
+          <el-input v-model="form.T_money" type="text" autocomplete="off" placeholder="请输入合同金额" class="w-50" />
+        </el-form-item>
+        <el-form-item label="合同备注:" :label-width="formLabelWidth" prop="T_remark">
+          <el-input
+            class="w-50"
+            v-model="form.T_remark"
+            :autosize="{ minRows: 4, maxRows: 6 }"
+            type="textarea"
+            placeholder="请输入备注信息"
+          />
+        </el-form-item>
+        <el-form-item label="上传附件:" :label-width="formLabelWidth" prop="T_type">
+          <Upload
+            class="w-50"
+            ref="uploadRef"
+            :isImg="true"
+            :limit="1"
+            v-model="form.T_pdf"
+            accept=".pdf"
+            listType="text"
+          >
+            <el-button type="primary">上传文件</el-button>
+            <template #tip> 只能上传pdf格式文件!!</template>
+          </Upload>
+        </el-form-item>
+
+        <div class="btn">
+          <el-divider>
+            <el-button>取消</el-button>
+            <el-button v-if="isNew" color="#626aef">提交</el-button>
+            <el-button v-else color="#626aef">修改</el-button>
+          </el-divider>
+        </div>
+      </el-form>
+    </Drawer>
+    <Dialog ref="dialogRef" width="80%">
+      <template #header> 选择产品 </template>
+      <TableBase
+        ref="TableProductRef"
+        :columns="productColumns"
+        :requestApi="Storehouse_Product_List"
+        :initParam="initParam"
+      >
+        <template #table-header>
+          <div class="input-suffix">
+            <el-row :gutter="20" style="margin-bottom: 0">
+              <el-col :xl="6" :lg="8" :md="10" class="d-flex">
+                <span class="inline-flex items-center">产品分类:</span>
+                <el-select v-model="initParam.T_class" clearable placeholder="请选择分类~">
+                  <el-option v-for="item in classOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
+                </el-select>
+              </el-col>
+              <el-col :xl="6" :lg="8" :md="10" class="d-flex">
+                <span class="inline-flex items-center">产品名称:</span>
+                <el-input
+                  v-model="initParam.T_name"
+                  type="text"
+                  placeholder="按产品名称、产品型号搜索"
+                  @change="searchHandle"
+                />
+              </el-col>
+              <el-col :xl="7" :lg="8" :md="12" class="d-flex">
+                <span class="inline-flex items-center">产品型号:</span>
+                <el-select v-model="initParam.T_model" clearable placeholder="请选择型号~">
+                  <el-option v-for="item in modelOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
+                </el-select>
+                <el-button type="primary" @click="searchHandle">搜索</el-button>
+              </el-col>
+            </el-row>
+          </div>
+        </template>
+        <template #T_img="{ row }">
+          <el-image style="height: 50px" :src="row.T_img" fit="cover" />
+        </template>
+        <template #T_relation_sn="{ row }">
+          <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
+          <el-tag v-else type="success" effect="dark">否</el-tag>
+        </template>
+      </TableBase>
+    </Dialog>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.contract-form {
+  :deep(.el-table--border .el-table__cell) {
+    border-right: 0;
+  }
+  :deep(.table-header),
+  :deep(.card) {
+    border: 0;
+  }
+  .btn {
+    margin-top: 32px;
+    display: flex;
+    justify-content: center;
+    .el-button {
+      padding: 0 32px;
+    }
+  }
+  .w-50 {
+    width: 21.5rem;
+  }
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .d-flex {
+      display: flex;
+    }
+  }
+}
+</style>

+ 16 - 16
vite.config.ts

@@ -22,22 +22,22 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     },
     server: {
       open: true,
-      host: '0.0.0.0',
-      proxy: {
-        '/api': {
-          target: 'https://erp.baozhida.cn',
-          changeOrigin: true,
-          rewrite: path => path.replace(/^\/api/, '/testapi')
-        },
-        '/testapi': {
-          target: 'https://erp.baozhida.cn',
-          changeOrigin: true
-        },
-        '/ams': {
-          target: 'http://erp.baozhida.cn',
-          changeOrigin: true
-        }
-      }
+      host: '0.0.0.0'
+      // proxy: {
+      //   '/api': {
+      //     target: 'https://erp.baozhida.cn',
+      //     changeOrigin: true,
+      //     rewrite: path => path.replace(/^\/api/, '/testapi')
+      //   },
+      //   '/testapi': {
+      //     target: 'https://erp.baozhida.cn',
+      //     changeOrigin: true
+      //   },
+      //   '/ams': {
+      //     target: 'http://erp.baozhida.cn',
+      //     changeOrigin: true
+      //   }
+      // }
     },
     plugins: [
       vue(),