소스 검색

添加仓库页面

yangg 1 년 전
부모
커밋
4445bf582b

+ 1 - 1
.env

@@ -1 +1 @@
-VITE_API_URL=http://192.168.8.101:9999
+VITE_API_URL=https://api.raycos.net

+ 6 - 0
package.json

@@ -12,6 +12,7 @@
     "@bhplugin/vue3-datatable": "^1.0.3",
     "@capacitor/android": "^6.0.0",
     "@capacitor/cli": "^6.0.0",
+    "@element-plus/icons-vue": "^2.3.1",
     "@fullcalendar/core": "^5.11.0",
     "@fullcalendar/daygrid": "^5.11.0",
     "@fullcalendar/interaction": "^5.11.0",
@@ -27,6 +28,7 @@
     "axios": "^1.6.8",
     "dotenv": "^16.4.5",
     "easymde": "^2.18.0",
+    "element-china-area-data": "^6.1.0",
     "element-plus": "^2.7.5",
     "file-upload-with-preview": "^4.2.0",
     "highlight.js": "^11.3.1",
@@ -36,8 +38,12 @@
     "jwt-decode": "^4.0.0",
     "maska": "^1.5.0",
     "path": "^0.12.7",
+    "path-browserify": "^1.0.1",
     "pinia": "^2.0.22",
+    "print-js": "^1.6.0",
     "psl": "^1.9.0",
+    "qrcode": "^1.5.3",
+    "qrcodejs2": "^0.0.2",
     "qs": "^6.12.1",
     "sweetalert2": "^11.5.1",
     "swiper": "^8.4.4",

+ 155 - 0
src/api/auth.js

@@ -0,0 +1,155 @@
+/**
+ * 权限管理相关API
+ */
+
+import request from '@/utils/request'
+import md5 from 'js-md5'
+
+/**
+ * 获取管理员列表
+ * @param params
+ */
+export function getAdministratorList(params) {
+  return request({
+    url: 'admin/systems/manager/admin-users',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 添加管理员
+ * @param params
+ */
+export function addAdministrator(params) {
+  return request({
+    url: 'admin/systems/manager/admin-users',
+    method: 'post',
+    data: params
+  })
+}
+
+/**
+ * 获取管理员详情
+ * @param id
+ */
+export function getAdministratorDetail(id) {
+  return request({
+    url: `admin/systems/manager/admin-users/${id}`,
+    method: 'get'
+  })
+}
+
+/**
+ * 修改管理员
+ * @param id
+ * @param parmas
+ */
+export function editAdministrator(id, parmas) {
+  return request({
+    url: `admin/systems/manager/admin-users/${id}`,
+    method: 'put',
+    data: parmas
+  })
+}
+
+/**
+ * 恢复理员
+ * @param id
+ */
+export function restoreAdministrator(id) {
+  return request({
+    url: `admin/systems/manager/admin-users/${id}/recover `,
+    method: 'put'
+  })
+}
+
+/**
+ * 删除管理员
+ * @param id
+ */
+export function deleteAdministrator(id) {
+  return request({
+    url: `admin/systems/manager/admin-users/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取角色列表
+ * @param params
+ */
+export function getRoleList(params) {
+  return request({
+    url: 'admin/systems/roles',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/**
+ * 删除角色
+ * @param id
+ */
+export function deleteRole(id) {
+  return request({
+    url: `admin/systems/roles/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取角色具体权限
+ * @param id
+ */
+export function getRolePermission(id) {
+  return request({
+    url: `admin/systems/roles/${id}`,
+    method: 'get'
+  })
+}
+
+/**
+ * 添加角色
+ * @param params
+ */
+export function addRole(params) {
+  return request({
+    url: 'admin/systems/roles',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 修改角色
+ * @param id
+ * @param params
+ */
+export function editRole(id, params) {
+  return request({
+    url: `admin/systems/roles/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 修改当前管理员的头像以及密码
+ * @param params
+ */
+export function editUserInfo(params) {
+  if (params.old_passwprd && params.password) {
+    params.old_passwprd = md5(params.old_passwprd)
+    params.password = md5(params.password)
+  }
+  return request({
+    url: 'admin/systems/admin-users',
+    method: 'put',
+    data: params
+  })
+}

+ 455 - 0
src/api/basicSetting.js

@@ -0,0 +1,455 @@
+/**
+ * 基础设置相关API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 添加门店
+ * @param store
+ */
+export function addStore(store) {
+  return request({
+    url: '/admin/erp/store',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: store
+  })
+}
+
+/**
+ * 编辑门店
+ * @param store
+ */
+export function editStore(id, store) {
+  return request({
+    url: `/admin/erp/store/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: store
+  })
+}
+
+/**
+ * 删除门店
+ * @param id
+ */
+export function deleteStore(id) {
+  return request({
+    url: `/admin/erp/store/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取门店列表
+ * @param params
+ */
+export function getBranches(params) {
+  return request({
+    url: '/admin/erp/store',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/**
+ * 获取品牌列表
+ * @param params
+ * @returns {Promise<any>}
+ */
+export function getBrandList(params) {
+  return request({
+    url: 'admin/goods/brands',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/**
+ * 获取所有品牌数据
+ * @returns
+ */
+export function getBrandAllList() {
+  return request({
+    url: 'admin/goods/brands/all',
+    method: 'get',
+    loading: false
+  })
+}
+
+/**
+ * 添加品牌
+ * @param params
+ */
+export function addBrand(params) {
+  return request({
+    url: 'admin/goods/brands',
+    method: 'post',
+    data: params
+  })
+}
+
+/**
+ * 编辑品牌
+ * @param id
+ * @param params
+ */
+export function editBrand(id, params) {
+  return request({
+    url: `admin/goods/brands/${id}`,
+    method: 'put',
+    data: params
+  })
+}
+
+/**
+ * 删除品牌
+ * @param ids
+ * @returns {Promise<any>}
+ */
+export function deleteBrand(ids) {
+  if (Array.isArray(ids)) ids = ids.join(',')
+  return request({
+    url: `admin/goods/brands/${ids}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 修改营销经理
+ * @param id
+ * @param parmas
+ */
+export function editMarketingManager(id, parmas) {
+  return request({
+    url: `admin/erp/marketingManager/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 新增营销经理
+ * @param id
+ * @param parmas
+ */
+export function addMarketingManager(parmas) {
+  return request({
+    url: `admin/erp/marketingManager`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 获取营销经理列表
+ * @param params
+ */
+export function getMarketingManagerList(params) {
+  return request({
+    url: 'admin/erp/marketingManager',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 获取编号规则列表
+ * @param params
+ */
+export function getNoGenerateRuleList(params) {
+  return request({
+    url: 'admin/erp/noGenerateRule',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 修改编号规则
+ * @param id
+ * @param parmas
+ */
+export function editNoGenerateRule(id, parmas) {
+  return request({
+    url: `admin/erp/noGenerateRule/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 添加编号规则
+ * @param params
+ */
+export function addNoGenerateRule(params) {
+  return request({
+    url: 'admin/erp/noGenerateRule',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 删除编号规则
+ * @param id
+ */
+export function deleteNoGenerateRule(id) {
+  return request({
+    url: `admin/erp/noGenerateRule/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取仓库列表
+ * @param params
+ */
+export function getWarehouseList(params) {
+  return request({
+    url: 'admin/erp/warehouse',
+    method: 'get',
+    params
+  })
+}
+
+/* 查询仓库 */
+
+export function getWarehouse(id) {
+  return request({
+    url: `/admin/erp/warehouse/${id}`,
+    method: 'get',
+
+  })
+}
+/**
+ * 修改仓库
+ * @param id
+ * @param parmas
+ */
+export function editWarehouse(id, parmas) {
+  return request({
+    url: `admin/erp/warehouse/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 添加仓库
+ * @param params
+ */
+export function addWarehouse(params) {
+  return request({
+    url: 'admin/erp/warehouse',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 删除仓库
+ * @param id
+ */
+export function deleteWarehouse(id) {
+  return request({
+    url: `admin/erp/warehouse/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 所有仓库
+ * @param params
+ */
+export function getWarehouseListAll(params) {
+  return request({
+    url: 'admin/erp/warehouse/list-all',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/**
+ * 所有正常状态的供应商
+ * @param params
+ */
+export function getSupplierListAll() {
+  return request({
+    url: 'admin/erp/supplier/list-all',
+    method: 'get',
+    loading: false
+  })
+}
+
+/**
+ * 获取供应商列表
+ * @param params
+ */
+export function getSupplierList(params) {
+  return request({
+    url: 'admin/erp/supplier',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/**
+ * 修改供应商
+ * @param id
+ * @param parmas
+ */
+export function editSupplier(id, parmas) {
+  return request({
+    url: `admin/erp/supplier/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 添加供应商
+ * @param params
+ */
+export function addSupplier(params) {
+  return request({
+    url: 'admin/erp/supplier',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 删除供应商
+ * @param id
+ */
+export function deleteSupplier(id) {
+  return request({
+    url: `admin/erp/supplier/${id}`,
+    method: 'delete'
+  })
+}
+
+// 获取仓库列表 /admin/erp/warehouse
+
+export function warehouse(params) {
+  return request({
+    url: 'admin/erp/warehouse',
+    method: 'get',
+    loading: false
+  })
+}
+
+//客户列表 /admin/erp/customer
+
+export function customer(params) {
+  return request({
+    url: '/admin/erp/customer',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+
+export function editCustomer(id, store) {
+  return request({
+    url: `/admin/erp/customer/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: store
+  })
+}
+
+// /admin/erp/customer
+export function addCustomer(params) {
+  return request({
+    url: '/admin/erp/customer',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+// s/admin/erp/customer/{id}
+export function deleteCustomer(id) {
+  return request({
+    url: `/admin/erp/customer/${id}`,
+    method: 'delete'
+  })
+}
+
+/* 获取销售人员 /admin/systems/manager/admin-users/getUserByDept*/
+export function getUserByDept(params) {
+  return request({
+    url: '/admin/systems/manager/admin-users/getUserByDept',
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+
+/* 查询客户信息  */
+
+export function getCustomer(id) {
+  return request({
+    url: `/admin/erp/customer/${id}`,
+    method: 'get',
+    loading: false,
+  })
+}
+
+/* 快递鸟创建门店 /admin/erp/kdn/createStore*/
+export function addCreateStore(params) {
+  return request({
+    url: '/admin/erp/kdn/createStore',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/* 修改*/
+export function updateStore(params) {
+  return request({
+    url: '/admin/erp/kdn/updateStore',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+/* 获取列表 */
+
+export function getList(params) {
+  return request({
+    url: `/admin/erp/kdn/list`,
+    method: 'get',
+    loading: false,
+    params
+  })
+}
+/* 
+/admin/erp/kdn/selectAudit */
+export function selectAudit(params) {
+  return request({
+    url: `/admin/erp/kdn/selectAudit`,
+    method: 'get',
+    loading: false,
+    params
+  })
+}

+ 338 - 0
src/api/goodsLend.js

@@ -0,0 +1,338 @@
+/**
+ * 商品借出相关API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 归还商品借出
+ * @param id
+ * @param parmas
+ */
+export function returnGoodsLend(id, parmas) {
+  return request({
+    url: `admin/lend/form/return/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 批量确认商品借出单
+ * @param id
+ */
+export function lendFormConfirm(ids) {
+  return request({
+    url: `admin/lend/form/confirm/${ids}`,
+    method: 'post'
+  })
+}
+
+/**
+ * 获取商品借出列表
+ * @param params
+ */
+export function getgoodsLendAddList(params) {
+  return request({
+    url: 'admin/lend/form',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 修改商品借出
+ * @param id
+ * @param parmas
+ */
+export function editgoodsLendAdd(id, parmas) {
+  return request({
+    url: `admin/lend/form/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 添加商品借出
+ * @param params
+ */
+export function addgoodsLendAdd(params) {
+  return request({
+    url: 'admin/lend/form',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 删除商品借出
+ * @param id
+ */
+export function deletegoodsLendAdd(id) {
+  return request({
+    url: `admin/lend/form/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 商品借出详情
+ * @param id
+ */
+export function getgoodsLendAddInfo(id) {
+  return request({
+    url: `admin/lend/form/${id}`,
+    method: 'get'
+  })
+}
+
+/**
+ * 审核商品借出
+ * @param params
+ */
+export function auditGoodsLend(id, parmas) {
+  return request({
+    url: `admin/lend/form/${id}/audit`,
+    method: 'post',
+    data: parmas
+  })
+}
+
+/**
+ * 获取商品借出详情
+ * @param params
+ */
+export function getGoodsLendInfo(id) {
+  return request({
+    url: `admin/lend/form/${id}`,
+    method: 'get',
+    loaidng: false
+  })
+}
+
+/**
+ * 查询待归还的借出单商品分页列表数据
+ * @param params
+ */
+export function getGoodsLendListWaitReturn(params) {
+  return request({
+    url: 'admin/lend/form/list-wait-return',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 获取商品借出列表
+ * @param params
+ */
+export function getGoodsLendList(params) {
+  return request({
+    url: 'admin/lend/form',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 添加商品借出
+ * @param params
+ */
+export function addGoodsLend(parmas) {
+  return request({
+    url: `admin/lend/form`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 更新商品借出
+ * @param id
+ * @param parmas
+ */
+export function editGoodsLend(id, parmas) {
+  return request({
+    url: `admin/lend/form/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除商品借出
+ * @param id
+ */
+export function deleteGoodsLend(ids) {
+  return request({
+    url: `admin/lend/form/${ids}`,
+    method: 'delete'
+  })
+}
+
+//待入库商品信息
+export function warehouseEntry(ids) {
+  return request({
+    url: `admin/erp/warehouseEntry`,
+    method: 'get'
+  })
+}
+
+// 获取库位列表
+export function query(ids) {
+  return request({
+    url: `/admin/erp/warehouse/area/query `,
+    method: 'get'
+  })
+}
+
+// 获取库位列表
+export function create(parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/create `,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+// 获取库位列表
+export function update(parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/update `,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除
+ * @param id
+ */
+export function deletes(parmas){
+  return request({
+    url: `/admin/erp/warehouse/area/delete`,
+    method: 'delete',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+//  /admin/sale/channel
+
+export function channel(params) {
+  return request({
+    url: `/admin/sale/channel`,
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+//渠道添加 /admin/sale/channel
+export function addChannel(parmas) {
+  return request({
+    url: `/admin/sale/channel`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+// 渠道修改/admin/sale/channel/${id}
+export function editChannel(id, parmas) {
+  return request({
+    url: `/admin/sale/channel/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+// 删除 /admin/sale/channel/{id}
+
+export function deleteChannel(ids) {
+  return request({
+    url: `/admin/sale/channel/${ids}`,
+    method: 'delete'
+  })
+}
+
+// 获取详情 /admin/erp/warehouse/area/info
+
+export function getInfo(parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/info`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/* 库位区域列表 */
+export function getListAreaType(params) {
+  return request({
+    url: `/admin/erp/warehouse/area/listAreaType`,
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/* 库位区域新增 */
+export function addCreateAreaType(parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/createAreaType`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/* 库位区域修改 */
+export function editUpdateAreaType(id, parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/updateAreaType/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/* 库位区域删除 */
+export function removeAreaType(ids) {
+  return request({
+    url: `/admin/erp/warehouse/area/removeAreaType/${ids}`,
+    method: 'delete'
+  })
+}
+
+/* 根据库位获取当前商品/admin/erp/warehouse/area/goodNotAreaList */
+export function goodNotAreaList(id,params) {
+  return request({
+    url: `/admin/erp/warehouse/area/goodNotAreaList/${id}`,
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/* 添加 /admin/erp/warehouse/area/bindAreaStock/{warehouseId}/{warehouseAreaId} */
+
+export function bindAreaStock(warehouseId,warehouseAreaId,parmas) {
+  return request({
+    url: `/admin/erp/warehouse/area/bindAreaStock/${warehouseId}/${warehouseAreaId}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}

+ 215 - 0
src/api/goodsTransfer.js

@@ -0,0 +1,215 @@
+/**
+ * 商品调拨相关API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 批量退回商品调拨
+ * @param params
+ */
+export function rejectGoodsTransfer(ids, parmas) {
+  return request({
+    url: `admin/erp/stockTransfer/${ids}/reject?handle_by_id=${parmas.in_handled_by_id}&handle_by_id_name=${parmas.in_handled_by_name}`,
+    method: 'post'
+  })
+}
+/**
+ * 批量确认商品调拨
+ * @param params
+ */
+export function successGoodsTransfer(ids, parmas) {
+  return request({
+    url: `admin/erp/stockTransfer/${ids}/confirm?handle_by_id=${parmas.in_handled_by_id}&handle_by_id_name=${parmas.in_handled_by_name}`,
+    method: 'post'
+  })
+}
+/**
+ * 提交商品调拨
+ * @param params
+ */
+export function submitGoodsTransfer(id) {
+  return request({
+    url: `admin/erp/stockTransfer/${id}/submit`,
+    method: 'post'
+  })
+}
+/**
+ * 撤回商品调拨
+ * @param params
+ */
+export function withdrawGoodsTransfer(id) {
+  return request({
+    url: `admin/erp/stockTransfer/${id}/withdraw`,
+    method: 'post'
+  })
+}
+/**
+ * 审核商品调拨
+ * @param params
+ */
+export function auditGoodsTransfer(id, parmas) {
+  return request({
+    url: `admin/erp/stockTransfer/${id}/audit`,
+    method: 'post',
+    data: parmas
+  })
+}
+
+/**
+ * 获取商品调拨详情
+ * @param params
+ */
+export function getGoodsTransferInfo(id) {
+  return request({
+    url: `admin/erp/stockTransfer/${id}`,
+    method: 'get',
+    loaidng: false
+  })
+}
+
+/**
+ * 获取商品调拨提交列表
+ * @param params
+ */
+export function getGoodsTransferApplyList(params) {
+  return request({
+    url: 'admin/erp/stockTransfer',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 获取商品调拨确认列表
+ * @param params
+ */
+export function getGoodsTransferConfirmList(params) {
+  return request({
+    url: 'admin/erp/stockTransfer/confirm-list',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 添加商品调拨
+ * @param params
+ */
+export function addGoodsTransfer(parmas) {
+  return request({
+    url: `admin/erp/stockTransfer`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 更新商品调拨
+ * @param id
+ * @param parmas
+ */
+export function editGoodsTransfer(id, parmas) {
+  return request({
+    url: `admin/erp/stockTransfer/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除商品调拨
+ * @param id
+ */
+/* export function deleteGoodsTransfer(ids) {
+  return request({
+    url: `admin/erp/stockTransfer/${ids}`,
+    method: 'delete'
+  })
+} */
+
+// 获取库存商品 /erp/productStock
+
+export function productStock(params) {
+  return request({
+    url: '/admin/erp/productStock',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+//库存列表删除/admin/erp/productStock/delete
+
+export function deleteGoodsTransfer(parmas) {
+  return request({
+    url: `/admin/erp/productStock/delete`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+
+/*查看 库存
+/admin/erp/productStock/getOne */
+export function getOne(params) {
+  return request({
+    url: '/admin/erp/productStock/getOne',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/* 回调 /admin/erp/stockTransfer/{id}/withdraw*/
+export function get_withdraw(id) {
+  return request({
+    url: `/admin/erp/stockTransfer/${id}/withdraw`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+
+  })
+}
+/* /admin/erp/product/updateWarnStock/{stockId}/{warnNum} */
+
+export function updateWarnStock(parmas) {
+  return request({
+    url: `/admin/erp/product/updateWarnStock`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data:parmas
+  })
+}
+
+
+/* 库存预警列表接口 /admin/erp/product/getWarnStock/{warehouseId}*/
+export function getWarnStock(warehouseId) {
+  return request({
+    url: `/admin/erp/product/getWarnStock/${warehouseId}`,
+    method: 'get',
+   
+  })
+}
+
+/* 获取在途明细 /admin/erp/product/transitDetail/{stockId} */
+export function transitDetail(stockId) {
+  return request({
+    url: `/admin/erp/product/transitDetail/${stockId}`,
+    method: 'get',
+   
+  })
+}
+
+/* /admin/erp/warehouseOut/getLockedStock */
+export function getLockedStock(params) {
+  return request({
+    url: `/admin/erp/warehouseOut/getLockedStock`,
+    method: 'get',
+    params
+  })
+}

+ 370 - 0
src/api/setting.js

@@ -0,0 +1,370 @@
+/**
+ * 设置相关API
+ */
+
+import request from '@/utils/request'
+
+/**
+ * 查询系统日志列表
+ * @param params
+ */
+export function getSystemLogs(params) {
+  return request({
+    url: 'admin/system-logs',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 获取岗位列表
+ * @param params
+ */
+export function getStationList(params) {
+  return request({
+    url: 'admin/erp/post',
+    method: 'get',
+    params
+  })
+}
+
+/**
+ * 获取岗位列表全部
+ * @param params
+ */
+export function getStationAllList() {
+  return request({
+    url: 'admin/erp/post/list-all',
+    method: 'get',
+    loaidng: false
+  })
+}
+
+/**
+ * 添加岗位
+ * @param params
+ */
+export function addStation(parmas) {
+  return request({
+    url: `admin/erp/post`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 更新岗位
+ * @param id
+ * @param parmas
+ */
+export function editStation(id, parmas) {
+  return request({
+    url: `admin/erp/post/${id}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除岗位
+ * @param id
+ */
+export function deleteStation(id) {
+  return request({
+    url: `admin/erp/post/${id}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 批量删除岗位
+ * @param id
+ */
+export function deletesStation(ids) {
+  return request({
+    url: `admin/erp/post/${ids}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取部门列表
+ * @param params
+ */
+export function getDeptList() {
+  return request({
+    url: 'admin/systems/dept/list',
+    method: 'get'
+  })
+}
+
+/**
+ * 添加部门
+ * @param params
+ */
+export function addDept(parmas) {
+  return request({
+    url: `admin/systems/dept/create?region=${parmas.region}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 更新部门
+ * @param id
+ * @param parmas
+ */
+export function editDept(parmas) {
+  return request({
+    url: `admin/systems/dept/update?region=${parmas.region}`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除部门
+ * @param id
+ */
+export function deleteDept(params) {
+  return request({
+    url: `admin/systems/dept/delete`,
+    method: 'delete',
+    params
+  })
+}
+
+/**
+ * 获取字典类型列表
+ * @param params
+ */
+export function getDictTypeList(params) {
+  return request({
+    url: 'admin/systems/dict-type/page',
+    method: 'get',
+    params
+  })
+}
+
+/**
+ * 添加字典类型
+ * @param params
+ */
+export function addDictType(params) {
+  return request({
+    url: 'admin/systems/dict-type/create',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 修改字典类型
+ * @param id
+ * @param parmas
+ */
+export function editDictType(parmas) {
+  return request({
+    url: `admin/systems/dict-type/update`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 删除字典类型
+ * @param id
+ */
+export function deleteDictType(params) {
+  return request({
+    url: `admin/systems/dict-type/delete`,
+    method: 'delete',
+    params
+  })
+}
+
+/**
+ * 获取指定字段类型字典数据列表
+ * @param params
+ */
+export function getDictDataInfo(dict_type) {
+  return request({
+    url: 'admin/systems/dict-data/list-all-simple',
+    method: 'get',
+    loaidng: false,
+    params: {
+      dict_type
+    }
+  })
+}
+
+/**
+ * 获取字典数据列表
+ * @param params
+ */
+export function getDictDataList(params) {
+  return request({
+    url: 'admin/systems/dict-data/page',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/**
+ * 修改字典数据
+ * @param id
+ * @param parmas
+ */
+export function editDictData(parmas) {
+  return request({
+    url: `admin/systems/dict-data/update`,
+    method: 'put',
+    headers: { 'Content-Type': 'application/json' },
+    data: parmas
+  })
+}
+
+/**
+ * 添加字典数据
+ * @param params
+ */
+export function addDictData(params) {
+  return request({
+    url: 'admin/systems/dict-data/create',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+/**
+ * 删除字典数据
+ * @param id
+ */
+export function deleteDictData(params) {
+  return request({
+    url: `admin/systems/dict-data/delete`,
+    method: 'delete',
+    params
+  })
+}
+
+
+// 获取序列号 /admin/erp/goods/sn
+export function getDictSn(params) {
+  return request({
+    url: '/admin/erp/goods/sn',
+    method: 'get',
+    params,
+    loaidng: false
+  })
+}
+
+// 新增序列号
+export function addCreateSnCode(params) {
+  return request({
+    url: '/admin/erp/goods/sn/createSnCode',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+// 批量生成 /admin/erp/goods/sn/save
+
+export function addSave(params) {
+  return request({
+    url: '/admin/erp/goods/sn/save',
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+
+// 修改  /admin/erp/goods/sn/update/{id}
+
+export function update(params) {
+  return request({
+    url: `/admin/erp/goods/sn/update/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    data: params
+  })
+}
+/* 获取详情  /admin/erp/goods/sn/getDetail/{id} */
+export function getDetail(id) {
+  return request({
+    url: `/admin/erp/goods/sn/getDetail/${id}`,
+    method: 'get',
+    loaidng: false
+  })
+}
+
+// 获取所有有sn的sku
+
+export function getSnProduct(params) {
+  return request({
+    url: '/admin/erp/goods/sn/getSnProduct',
+    method: 'get',
+    params
+  })
+}
+
+//获取所有仓库
+export function getListAll() {
+  return request({
+    url: '/admin/erp/warehouse/list-all',
+    method: 'get',
+    loaidng: false
+  })
+}
+
+//字典接口 /admin/systems/dict-data/page
+export function getPage(params) {
+  return request({
+    url: '/admin/systems/dict-data/page',
+    method: 'get',
+    loaidng: false,
+    params
+  })
+}
+
+/* /admin/erp/goods/sn/updateSnCode/{id}/{snCode} */
+
+export function updateSnCode(id,snCode) {
+  return request({
+    url: `/admin/erp/goods/sn/updateSnCode/${id}/${snCode}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+   
+  })
+}
+//
+export function getPayUrl(params) {
+  return request({
+    url: `/admin/erp/pay/getPayUrl`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    needToken: false,
+    data: params
+  })
+}
+///admin/erp/pay/getOpenIdCode/
+
+export function getOpenIdCode(id,params) {
+  return request({
+    url: `/admin/erp/pay/getOpenIdCode/${id}`,
+    method: 'post',
+    headers: { 'Content-Type': 'application/json' },
+    needToken: false,
+    data: params
+  })
+}

+ 154 - 0
src/components/TableLayout/index.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="containers dark:bg-[#060818] p-3" v-loading="loading">
+    <div class="tips-info" v-if="tips">
+      <slot name="tips"></slot>
+    </div>
+    <div v-if="toolbar" class="toolbar">
+      <slot name="toolbar"></slot>
+    </div>
+    <el-table
+      :max-height="height"
+      v-bind="$attrs"
+      v-on="$listeners"
+      ref="table"
+      align="center"
+      :data="tableData"
+      :row-key="rowKey"
+      :stripe="stripe"
+      :header-cell-style="{ textAlign: 'center' }"
+      :cell-style="{textAlign: 'center'}"
+      :style="{ width: '100%', minHeight: height + 'px' }"
+      @selection-change="selectionChange"
+      highlight-current-row
+    >
+      <slot name="table-columns"></slot>
+    </el-table>
+    <div v-if="pagination" class="pagination">
+      <div class="pagination-toolbar">
+        <slot name="pagination-toolbar"></slot>
+      </div>
+      <slot name="pagination"></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'EnTableLayout',
+  props: {
+    /** 是否显示斑纹 */
+    stripe: {
+      type: Boolean,
+      default: true,
+    },
+    /** 行数据的 Key */
+    rowKey: {
+      type: [String, Function],
+      default: null,
+    },
+    /** 是否显示工具栏 */
+    toolbar: {
+      type: Boolean,
+      default: true,
+    },
+    /** 是否显示操作提示 */
+    tips: {
+      type: Boolean,
+      default: false,
+    },
+    /** 是否显示分页 */
+    pagination: {
+      type: Boolean,
+      default: true,
+    },
+    /** 表格数据 */
+    tableData: {
+      default: function () {
+        return [];
+      },
+    },
+    /** 加载状态 */
+    loading: {
+      type: Boolean,
+      default: false,
+    },
+    /** 当选择项发生变化 */
+    selectionChange: {
+      type: Function,
+      default: function () {
+        return [];
+      },
+    },
+  },
+  data() {
+    return {
+      height: `${document.body.clientHeight - 260}`,
+    };
+  },
+};
+</script>
+
+<style  scoped>
+.containers {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background: #fff;
+}
+
+/** 工具栏样式 */
+.toolbar {
+  display: flex;
+  align-items: center;
+  height: 44px;
+  border-bottom: 1px solid #e6ebf5;
+  background-color: #fff;
+}
+
+.inner-toolbar {
+  display: flex;
+  width: 100%;
+  justify-content: space-between;
+  padding: 0 20px;
+}
+
+.toolbar-search {
+  margin-right: 10px;
+}
+
+/** 分页样式 */
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  padding: 5px 20px;
+  text-align: right;
+  background-color: #ffffff;
+}
+.pagination-toolbar {
+  display: flex;
+  align-items: center;
+}
+
+/** 操作提示样式 */
+.tips-info {
+  background: #fff;
+  color: #31708f;
+  padding: 1px 10px;
+  margin-bottom: 10px;
+  border-radius: 4px;
+}
+h4 {
+  font-weight: 500;
+  margin: 20px 0;
+}
+
+ul {
+  margin: 20px 0;
+  padding-left: 40px;
+  display: block;
+}
+li {
+  font-size: 12px;
+  list-style-type: disc;
+}
+</style>

+ 122 - 0
src/components/TableSearch/index.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="en-search">
+    <div style="width: 330px">
+      <el-input v-if="true" size="small" clearable :placeholder="placeholder" v-model="keyword" @keyup.enter="search">
+        <template #append>
+          <el-button @click="search"
+            ><el-icon style="vertical-align: middle">
+              <Search />
+            </el-icon>
+            <span style="vertical-align: middle"> </span></el-button
+          >
+        </template>
+      </el-input>
+    </div>
+    <div v-if="advanced">
+      <el-popover ref="popover" placement="bottom-start" :width="advancedWidth" v-model:visible="popoverVisible">
+        <slot name="advanced-content"></slot>
+        <div style="text-align: right; margin: 0">
+          <el-popover placement="top" width="160" v-model:visible="visibleDelPopover">
+            <p>确定要清空表单吗?</p>
+            <div style="text-align: right; margin: 0">
+              <el-button size="mini" type="text" @click="visibleDelPopover = false">取消</el-button>
+              <el-button type="primary" size="mini" @click="handleCleanForm">确定</el-button>
+            </div>
+            <template #reference>
+              <el-button size="mini" type="text" class="clean-form">清空</el-button>
+            </template>
+          </el-popover>
+          <el-button size="mini" type="text" @click="popoverVisible = false">取消</el-button>
+          <el-button type="primary" size="mini" @click="advancedSearch">确定</el-button>
+        </div>
+        <template #reference>
+          <el-button size="small" style="margin-left: 10px">高级搜索</el-button>
+        </template>
+      </el-popover>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, defineProps, getCurrentInstance } from 'vue';
+
+const props = defineProps<{
+  inputSearch?: boolean;
+  advanced?: boolean;
+  advancedWidth?: number;
+  placeholder?: string;
+}>();
+
+const keyword = ref('');
+const popoverVisible = ref(false);
+const visibleDelPopover = ref(false);
+const clearFuncs = ref<Function[]>([]);
+const clearFuncNames = ref(['clearValue', 'deleteSelected']);
+
+const { emit } = getCurrentInstance();
+
+const search = () => {
+  popoverVisible.value = false;
+  emit('search', keyword.value);
+};
+
+const advancedSearch = () => {
+  visibleDelPopover.value = false;
+  popoverVisible.value = false;
+  emit('advancedSearch', keyword.value);
+};
+
+const handleCleanForm = (event: Event) => {
+  const $parent = getCurrentInstance()?.proxy?.$parent?.$parent;
+  if (!$parent?.$refs['advancedForm']) {
+    $parent = $parent?.$parent?.$parent?.$parent;
+  }
+  const $form = $parent?.$refs['advancedForm'];
+  const $forms = $parent?.$refs['advancedForms'];
+  const objs = JSON.parse(JSON.stringify($parent?.advancedForm));
+  Object.keys(objs).forEach((key) => {
+    $parent.advancedForm[key] = '';
+  });
+  if ($forms) {
+    const objss = JSON.parse(JSON.stringify($parent?.advancedForms));
+    Object.keys(objss).forEach((key) => {
+      $parent.advancedForms[key] = '';
+    });
+    findClearValue($forms);
+  }
+
+  findClearValue($form);
+  clearFuncs.value.forEach((func) => {
+    func(event);
+  });
+  clearFuncs.value = [];
+  visibleDelPopover.value = false;
+};
+
+const findClearValue = (component: any) => {
+  const $ch = component.$children;
+  $ch &&
+    $ch.length &&
+    $ch.forEach((item: any) => {
+      const funs = clearFuncNames.value.filter((name) => typeof item[name] === 'function');
+      if (funs.length) {
+        clearFuncs.value.push(item[funs[0]]);
+      } else {
+        findClearValue(item);
+      }
+    });
+};
+</script>
+
+<style scoped>
+.en-search {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.clean-form {
+  color: #f56c6c;
+  margin-right: 10px;
+}
+</style>

+ 223 - 217
src/components/layout/Header.vue

@@ -1,85 +1,100 @@
 <template>
-    <header class="z-40" :class="{ dark: store.state.semidark && store.state.menu === 'horizontal' }">
-        <div class="shadow-sm">
-            <div class="relative bg-white flex w-full items-center px-5 py-2.5 dark:bg-[#0e1726]">
-                <div class="horizontal-logo flex lg:hidden justify-between items-center ltr:mr-2 rtl:ml-2">
-                    <router-link to="/" class="main-logo flex items-center shrink-0">
-                        <img class="w-8 ltr:-ml-1 rtl:-mr-1 inline" src="/src/assets/images/logo.png" alt="" />
-                        <span
-                            class="text-2xl ltr:ml-1.5 rtl:mr-1.5 font-semibold align-middle hidden md:inline dark:text-white-light transition-all duration-300">V3studio
-                            App</span>
-
-                    </router-link>
-                    <!-- 原导航 -->
-                    <a href="javascript:;"
-                        class="collapse-icon flex-none dark:text-[#d0d2d6] hover:text-primary dark:hover:text-primary flex lg:hidden ltr:ml-2 rtl:mr-2 p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
-                        @click="toggleSidebars">
-                        <icon-menu class="w-5 h-5" />
-                    </a>
-                </div>
-
-                <!-- 头部中间功能--待开发 -->
-                <div class="ltr:mr-2 rtl:ml-2 hidden sm:block">
-                    <ul class="flex items-center space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]">
-                        <li>
-                            <router-link to="/apps/calendar"
-                                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60">
-                                <icon-calendar />
-                            </router-link>
-                        </li>
-                        <li>
-                            <router-link to="/apps/todolist"
-                                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60">
-                                <icon-edit />
-                            </router-link>
-                        </li>
-                        <li>
-                            <router-link to="/apps/chat"
-                                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60">
-                                <icon-chat-notification />
-                            </router-link>
-                        </li>
-                    </ul>
-                </div>
-                <div
-                    class="sm:flex-1 ltr:sm:ml-0 ltr:ml-auto sm:rtl:mr-0 rtl:mr-auto flex items-center space-x-1.5 lg:space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]">
-                    <div class="sm:ltr:mr-auto sm:rtl:ml-auto">
-                        <form
-                            class="sm:relative absolute inset-x-0 sm:top-0 top-1/2 sm:translate-y-0 -translate-y-1/2 sm:mx-0 mx-4 z-10 sm:block hidden"
-                            :class="{ '!block': search }" @submit.prevent="search = false">
-
-                        </form>
-
-                        <button type="button"
-                            class="search_btn sm:hidden p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
-                            @click="search = !search">
-                            <icon-search class="w-4.5 h-4.5 mx-auto dark:text-[#d0d2d6]" />
-                        </button>
-                    </div>
-                    <div>
-                        <a href="javascript:;" v-show="store.state.theme === 'light'"
-                            class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
-                            @click="toggleTheme('dark')">
-                            <icon-sun />
-                        </a>
-                        <a href="javascript:;" v-show="store.state.theme === 'dark'"
-                            class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
-                            @click="toggleTheme('system')">
-                            <icon-moon />
-                        </a>
-                        <a href="javascript:;" v-show="store.state.theme === 'system'"
-                            class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
-                            @click="toggleTheme('dark')">
-                            <!-- <icon-laptop /> -->
-                            <icon-sun />
-
-                        </a>
-                    </div>
-
+  <header class="z-40" :class="{ dark: store.state.semidark && store.state.menu === 'horizontal' }">
+    <div class="shadow-sm">
+      <div class="relative bg-white flex w-full items-center px-5 py-2.5 dark:bg-[#0e1726]">
+        <div class="horizontal-logo flex lg:hidden justify-between items-center ltr:mr-2 rtl:ml-2">
+          <router-link to="/dashboard" class="main-logo flex items-center shrink-0">
+            <img class="w-full h-12 ltr:-ml-1 rtl:-mr-1 inline" src="https://www.raycos.com.cn/theme/m049/images/logo.png" alt="" />
+            <!-- <span class="text-2xl ltr:ml-1.5 rtl:mr-1.5 font-semibold align-middle hidden md:inline dark:text-white-light transition-all duration-300"
+              >V3studio App</span
+            > -->
+          </router-link>
+          <!-- 原导航 -->
+          <a
+            href="javascript:;"
+            class="collapse-icon flex-none dark:text-[#d0d2d6] hover:text-primary dark:hover:text-primary flex lg:hidden ltr:ml-2 rtl:mr-2 p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
+            @click="toggleSidebars"
+          >
+            <icon-menu class="w-5 h-5" />
+          </a>
+        </div>
 
+        <!-- 头部中间功能--待开发 -->
+        <div class="ltr:mr-2 rtl:ml-2 hidden sm:block">
+          <ul class="flex items-center space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]">
+            <li>
+              <router-link
+                to="/apps/calendar"
+                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              >
+                <icon-calendar />
+              </router-link>
+            </li>
+            <li>
+              <router-link
+                to="/apps/todolist"
+                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              >
+                <icon-edit />
+              </router-link>
+            </li>
+            <li>
+              <router-link
+                to="/apps/chat"
+                class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              >
+                <icon-chat-notification />
+              </router-link>
+            </li>
+          </ul>
+        </div>
+        <div
+          class="sm:flex-1 ltr:sm:ml-0 ltr:ml-auto sm:rtl:mr-0 rtl:mr-auto flex items-center space-x-1.5 lg:space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]"
+        >
+          <div class="sm:ltr:mr-auto sm:rtl:ml-auto">
+            <form
+              class="sm:relative absolute inset-x-0 sm:top-0 top-1/2 sm:translate-y-0 -translate-y-1/2 sm:mx-0 mx-4 z-10 sm:block hidden"
+              :class="{ '!block': search }"
+              @submit.prevent="search = false"
+            ></form>
 
+            <button
+              type="button"
+              class="search_btn sm:hidden p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
+              @click="search = !search"
+            >
+              <icon-search class="w-4.5 h-4.5 mx-auto dark:text-[#d0d2d6]" />
+            </button>
+          </div>
+          <div>
+            <a
+              href="javascript:;"
+              v-show="store.state.theme === 'light'"
+              class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              @click="toggleTheme('dark')"
+            >
+              <icon-sun />
+            </a>
+            <a
+              href="javascript:;"
+              v-show="store.state.theme === 'dark'"
+              class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              @click="toggleTheme('system')"
+            >
+              <icon-moon />
+            </a>
+            <a
+              href="javascript:;"
+              v-show="store.state.theme === 'system'"
+              class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
+              @click="toggleTheme('dark')"
+            >
+              <!-- <icon-laptop /> -->
+              <icon-sun />
+            </a>
+          </div>
 
-                    <!-- <div class="dropdown shrink-0">
+          <!-- <div class="dropdown shrink-0">
                         <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'"
                             offsetDistance="8">
                             <button type="button"
@@ -155,57 +170,46 @@
 </Popper>
 </div> -->
 
-                    <div class="dropdown shrink-0">
-                        <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'"
-                            offsetDistance="8" class="!block">
-                            <button type="button" class="relative group block">
-                                <img class="w-9 h-9 rounded-full object-cover saturate-50 group-hover:saturate-100"
-                                    src="/assets/images/me.jpg" alt="" />
-                            </button>
-                            <template #content="{ close }">
-                                <ul
-                                    class="text-dark dark:text-white-dark !py-0 w-[230px] font-semibold dark:text-white-light/90">
-                                    <li>
-                                        <div class="flex items-center px-4 py-4">
-                                            <div class="flex-none">
-                                                <img class="rounded-md w-10 h-10 object-cover"
-                                                    src="/assets/images/me.jpg" alt="" />
-                                            </div>
-                                            <div class="ltr:pl-4 rtl:pr-4 truncate">
-                                                <h4 class="text-base">
-                                                    Johnny Liang<span
-                                                        class="text-xs bg-success-light rounded text-success px-1 ltr:ml-2 rtl:ml-2">Pro</span>
-                                                </h4>
-                                                <a class="text-black/60 hover:text-primary dark:text-dark-light/60 dark:hover:text-white"
-                                                    href="javascript:;">johndoe@gmail.com</a>
-                                            </div>
-                                        </div>
-                                    </li>
-                                    <li>
-                                        <router-link to="/users/profile" class="dark:hover:text-white" @click="close()">
-                                            <icon-user class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 shrink-0" />
-
-                                            个人信息
-                                        </router-link>
-                                    </li>
-
-
-                                    <li class="border-t border-white-light dark:border-white-light/10">
-                                        <div class="text-danger !py-3" @click="handleLogoutClick">
-                                            <icon-logout class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 rotate-90 shrink-0" />
-                                            退出
-                                        </div>
-                                    </li>
-                                </ul>
-                            </template>
-                        </Popper>
+          <div class="dropdown shrink-0">
+            <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'" offsetDistance="8" class="!block">
+              <button type="button" class="relative group block">
+                <img class="w-9 h-9 rounded-full object-cover saturate-50 group-hover:saturate-100" src="/assets/images/me.jpg" alt="" />
+              </button>
+              <template #content="{ close }">
+                <ul class="text-dark dark:text-white-dark !py-0 w-[230px] font-semibold dark:text-white-light/90">
+                  <li>
+                    <div class="flex items-center px-4 py-4">
+                      <div class="flex-none">
+                        <img class="rounded-md w-10 h-10 object-cover" src="/assets/images/me.jpg" alt="" />
+                      </div>
+                      <div class="ltr:pl-4 rtl:pr-4 truncate">
+                        <h4 class="text-base">Johnny Liang<span class="text-xs bg-success-light rounded text-success px-1 ltr:ml-2 rtl:ml-2">Pro</span></h4>
+                        <a class="text-black/60 hover:text-primary dark:text-dark-light/60 dark:hover:text-white" href="javascript:;">johndoe@gmail.com</a>
+                      </div>
                     </div>
-                </div>
-            </div>
+                  </li>
+                  <li>
+                    <router-link to="/users/profile" class="dark:hover:text-white" @click="close()">
+                      <icon-user class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 shrink-0" />
 
+                      个人信息
+                    </router-link>
+                  </li>
 
+                  <li class="border-t border-white-light dark:border-white-light/10">
+                    <div class="text-danger !py-3" @click="handleLogoutClick">
+                      <icon-logout class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 rotate-90 shrink-0" />
+                      退出
+                    </div>
+                  </li>
+                </ul>
+              </template>
+            </Popper>
+          </div>
         </div>
-    </header>
+      </div>
+    </div>
+  </header>
 </template>
 
 <script lang="ts" setup>
@@ -243,137 +247,139 @@ import IconMenuDatatables from '@/components/icon/menu/icon-menu-datatables.vue'
 import IconMenuForms from '@/components/icon/menu/icon-menu-forms.vue';
 import IconMenuPages from '@/components/icon/menu/icon-menu-pages.vue';
 import IconMenuMore from '@/components/icon/menu/icon-menu-more.vue';
-import { ElMessageBox } from 'element-plus'
+import { ElMessageBox } from 'element-plus';
 //ElMessageBox
 const store = useStore();
 const route = useRoute();
-const router = useRouter()
+const router = useRouter();
 const search = ref(false);
 
 // multi language
 const i18n = reactive(useI18n());
 const changeLanguage = (item: any) => {
-    i18n.locale = item.code;
-    appSetting.toggleLanguage(item);
+  i18n.locale = item.code;
+  appSetting.toggleLanguage(item);
 };
 const currentFlag = computed(() => {
-    return `/assets/images/flags/${i18n.locale.toUpperCase()}.svg`;
+  return `/assets/images/flags/${i18n.locale.toUpperCase()}.svg`;
 });
 
 const notifications = ref([
-    {
-        id: 1,
-        profile: 'user-profile.jpeg',
-        message: '<strong class="text-sm mr-1">John Doe</strong>invite you to <strong>Prototyping</strong>',
-        time: '45 min ago',
-    },
-    {
-        id: 2,
-        profile: 'profile-34.jpeg',
-        message: '<strong class="text-sm mr-1">Adam Nolan</strong>mentioned you to <strong>UX Basics</strong>',
-        time: '9h Ago',
-    },
-    {
-        id: 3,
-        profile: 'profile-16.jpeg',
-        message: '<strong class="text-sm mr-1">Anna Morgan</strong>Upload a file',
-        time: '9h Ago',
-    },
+  {
+    id: 1,
+    profile: 'user-profile.jpeg',
+    message: '<strong class="text-sm mr-1">John Doe</strong>invite you to <strong>Prototyping</strong>',
+    time: '45 min ago',
+  },
+  {
+    id: 2,
+    profile: 'profile-34.jpeg',
+    message: '<strong class="text-sm mr-1">Adam Nolan</strong>mentioned you to <strong>UX Basics</strong>',
+    time: '9h Ago',
+  },
+  {
+    id: 3,
+    profile: 'profile-16.jpeg',
+    message: '<strong class="text-sm mr-1">Anna Morgan</strong>Upload a file',
+    time: '9h Ago',
+  },
 ]);
 
 const messages = ref([
-    {
-        id: 1,
-        image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-success-light dark:bg-success text-success dark:text-success-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span>',
-        title: 'Congratulations!',
-        message: 'Your OS has been updated.',
-        time: '1hr',
-    },
-    {
-        id: 2,
-        image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-info-light dark:bg-info text-info dark:text-info-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>',
-        title: 'Did you know?',
-        message: 'You can switch between artboards.',
-        time: '2hr',
-    },
-    {
-        id: 3,
-        image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-danger-light dark:bg-danger text-danger dark:text-danger-light"> <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></span>',
-        title: 'Something went wrong!',
-        message: 'Send Reposrt',
-        time: '2days',
-    },
-    {
-        id: 4,
-        image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-warning-light dark:bg-warning text-warning dark:text-warning-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">    <circle cx="12" cy="12" r="10"></circle>    <line x1="12" y1="8" x2="12" y2="12"></line>    <line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span>',
-        title: 'Warning',
-        message: 'Your password strength is low.',
-        time: '5days',
-    },
+  {
+    id: 1,
+    image:
+      '<span class="grid place-content-center w-9 h-9 rounded-full bg-success-light dark:bg-success text-success dark:text-success-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span>',
+    title: 'Congratulations!',
+    message: 'Your OS has been updated.',
+    time: '1hr',
+  },
+  {
+    id: 2,
+    image:
+      '<span class="grid place-content-center w-9 h-9 rounded-full bg-info-light dark:bg-info text-info dark:text-info-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>',
+    title: 'Did you know?',
+    message: 'You can switch between artboards.',
+    time: '2hr',
+  },
+  {
+    id: 3,
+    image:
+      '<span class="grid place-content-center w-9 h-9 rounded-full bg-danger-light dark:bg-danger text-danger dark:text-danger-light"> <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></span>',
+    title: 'Something went wrong!',
+    message: 'Send Reposrt',
+    time: '2days',
+  },
+  {
+    id: 4,
+    image:
+      '<span class="grid place-content-center w-9 h-9 rounded-full bg-warning-light dark:bg-warning text-warning dark:text-warning-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">    <circle cx="12" cy="12" r="10"></circle>    <line x1="12" y1="8" x2="12" y2="12"></line>    <line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span>',
+    title: 'Warning',
+    message: 'Your password strength is low.',
+    time: '5days',
+  },
 ]);
 
 onMounted(() => {
-    setActiveDropdown();
+  setActiveDropdown();
 });
 
 watch(route, (to, from) => {
-    setActiveDropdown();
+  setActiveDropdown();
 });
 
 const setActiveDropdown = () => {
-    const selector = document.querySelector('ul.horizontal-menu a[href="' + window.location.pathname + '"]');
-    if (selector) {
-        selector.classList.add('active');
-        const all: any = document.querySelectorAll('ul.horizontal-menu .nav-link.active');
-        for (let i = 0; i < all.length; i++) {
-            all[0]?.classList.remove('active');
-        }
-        const ul: any = selector.closest('ul.sub-menu');
-        if (ul) {
-            let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link');
-            if (ele) {
-                ele = ele[0];
-                setTimeout(() => {
-                    ele?.classList.add('active');
-                });
-            }
-        }
+  const selector = document.querySelector('ul.horizontal-menu a[href="' + window.location.pathname + '"]');
+  if (selector) {
+    selector.classList.add('active');
+    const all: any = document.querySelectorAll('ul.horizontal-menu .nav-link.active');
+    for (let i = 0; i < all.length; i++) {
+      all[0]?.classList.remove('active');
+    }
+    const ul: any = selector.closest('ul.sub-menu');
+    if (ul) {
+      let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link');
+      if (ele) {
+        ele = ele[0];
+        setTimeout(() => {
+          ele?.classList.add('active');
+        });
+      }
     }
+  }
 };
 
 const removeNotification = (value: number) => {
-    notifications.value = notifications.value.filter((d) => d.id !== value);
+  notifications.value = notifications.value.filter((d) => d.id !== value);
 };
 
 const removeMessage = (value: number) => {
-    messages.value = messages.value.filter((d) => d.id !== value);
+  messages.value = messages.value.filter((d) => d.id !== value);
 };
 const toggleSidebars = () => {
-    store.dispatch('toggleSidebar');
+  store.dispatch('toggleSidebar');
 };
 
 const toggleTheme = (theme) => {
-    store.dispatch('toggleTheme', theme);
+  store.dispatch('toggleTheme', theme);
 };
 const close = () => {
-    console.log("001");
-}
+  console.log('001');
+};
 const handleLogoutClick = () => {
-    ElMessageBox.confirm(
-        '确定退出吗?',
-        '提示',
-        {
-            confirmButtonText: '确定',
-            cancelButtonText: '取消',
-            type: 'warning',
-        }
-    )
-        .then(() => {
-            store.dispatch('logoutAction').then(() => {
-                const route = router.resolve('/login');
-                location.replace(route.href); // 为了重新实例化vue-router对象 避免bug
-            });
-        })
-        .catch(() => { /* Handle cancel action if needed */ });
+  ElMessageBox.confirm('确定退出吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(() => {
+      store.dispatch('logoutAction').then(() => {
+        const route = router.resolve('/login');
+        location.replace(route.href); // 为了重新实例化vue-router对象 避免bug
+      });
+    })
+    .catch(() => {
+      /* Handle cancel action if needed */
+    });
 };
 </script>

+ 157 - 511
src/components/layout/Sidebar.vue

@@ -1,503 +1,143 @@
 <template>
-    <div :class="{ 'dark text-white-dark': store.state.semidark }">
-        <nav
-            class="sidebar fixed min-h-screen h-full top-0 bottom-0 w-[260px] shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-50 transition-all duration-300">
-            <div class="bg-white dark:bg-[#0e1726] h-full">
-                <div class="flex justify-between items-center px-4 py-3">
-                    <router-link to="/" class="main-logo flex items-center shrink-0">
-                        <img class="w-8 ml-[5px] flex-none" src="/assets/images/logo.png" alt="" />
-                        <span
-                            class="text-2xl ltr:ml-1.5 rtl:mr-1.5 font-semibold align-middle lg:inline dark:text-white-light">V3Studio</span>
-                    </router-link>
-                    <a href="javascript:;"
-                        class="collapse-icon w-8 h-8 rounded-full flex items-center hover:bg-gray-500/10 dark:hover:bg-dark-light/10 dark:text-white-light transition duration-300 rtl:rotate-180 hover:text-primary"
-                        @click="toggleSidebars">
-                        <icon-carets-down class="m-auto rotate-90" />
-                    </a>
+  <div :class="{ 'dark text-white-dark': store.state.semidark }">
+    <nav class="sidebar fixed min-h-screen h-full top-0 bottom-0 w-[260px] shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-50 transition-all duration-300">
+      <div class="bg-white dark:bg-[#0e1726] h-full">
+        <div class="flex justify-between items-center px-4 py-3">
+          <router-link to="/dashboard" class="main-logo flex items-center shrink-0">
+            <img class="w-full h-12 ml-[5px] flex-none" src="https://www.raycos.com.cn/theme/m049/images/logo.png" alt="" />
+            <!-- <span class="text-2xl ltr:ml-1.5 rtl:mr-1.5 font-semibold align-middle lg:inline dark:text-white-light">V3Studio</span> -->
+          </router-link>
+          <a
+            href="javascript:;"
+            class="collapse-icon w-8 h-8 rounded-full flex items-center hover:bg-gray-500/10 dark:hover:bg-dark-light/10 dark:text-white-light transition duration-300 rtl:rotate-180 hover:text-primary"
+            @click="toggleSidebars"
+          >
+            <icon-carets-down class="m-auto rotate-90" />
+          </a>
+        </div>
+        <perfect-scrollbar
+          :options="{
+            swipeEasing: true,
+            wheelPropagation: false,
+          }"
+          class="h-[calc(100vh-80px)] relative"
+        >
+          <!-- <el-scrollbar wrapClass="scrollbar-wrapper">
+            <el-menu mode="vertical" :show-timeout="200" :default-active="$route.path" :collapse="isCollapse">
+              <sidebar-item v-for="route in permissionRouters" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
+            </el-menu>
+          </el-scrollbar> -->
+          <ul class="relative font-semibold space-y-0.5 p-4 py-0">
+            <li class="menu nav-item">
+              <button
+                type="button"
+                class="nav-link group w-full"
+                :class="{ active: activeDropdown === 'dashboard' }"
+                @click="activeDropdown === 'dashboard' ? (activeDropdown = null) : (activeDropdown = 'dashboard')"
+              >
+                <div class="flex items-center">
+                  <icon-menu-dashboard class="group-hover:!text-primary shrink-0" />
+                  <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">
+                    {{ $t('warehouse') }}
+                  </span>
                 </div>
-                <perfect-scrollbar :options="{
-                    swipeEasing: true,
-                    wheelPropagation: false,
-                }" class="h-[calc(100vh-80px)] relative">
-                    <ul class="relative font-semibold space-y-0.5 p-4 py-0">
-                        <li class="menu nav-item">
-                            <button type="button" class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'dashboard' }"
-                                @click="activeDropdown === 'dashboard' ? (activeDropdown = null) : (activeDropdown = 'dashboard')">
-                                <div class="flex items-center">
-                                    <icon-menu-dashboard class="group-hover:!text-primary shrink-0" />
-                                    <span
-                                        class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">
-                                        {{ $t('dashboard') }}
-                                    </span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'dashboard' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'dashboard'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/" @click="toggleMobileMenu">{{ $t('sales') }}</router-link>
-                                    </li>
-
-
-                                </ul>
-                            </vue-collapsible>
-                        </li>
-
-
-
-                        <li class="nav-item">
-                            <ul>
-
-                                <li class="menu nav-item">
-                                    <button type="button" class="nav-link group w-full"
-                                        :class="{ active: activeDropdown === 'invoice' }"
-                                        @click="activeDropdown === 'invoice' ? (activeDropdown = null) : (activeDropdown = 'invoice')">
-                                        <div class="flex items-center">
-                                            <icon-menu-chat class="group-hover:!text-primary shrink-0" />
-
-                                            <span
-                                                class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">
-                                                应用列表
-                                            </span>
-                                        </div>
-                                        <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'invoice' }">
-                                            <icon-caret-down />
-                                        </div>
-                                    </button>
-                                    <vue-collapsible :isOpen="activeDropdown === 'invoice'">
-                                        <ul class="sub-menu text-gray-500">
-                                            <li>
-                                                <router-link to="/login"
-                                                    @click="toggleMobileMenu">所有应用</router-link>
-                                            </li>
-                                            <li>
-                                                <router-link to="/apps/custom"
-                                                    @click="toggleMobileMenu">客户列表</router-link>
-                                            </li>
-                                            <li>
-                                                <router-link to="/apps/stock"
-                                                    @click="toggleMobileMenu">仓库列表</router-link>
-                                            </li>
-                                            <li>
-                                                <router-link to="/apps/order"
-                                                    @click="toggleMobileMenu">订单列表</router-link>
-                                            </li>
-                                            <li>
-                                                <router-link to="/apps/goods"
-                                                    @click="toggleMobileMenu">商品列表</router-link>
-                                            </li>
-                                        </ul>
-                                    </vue-collapsible>
-                                </li>
-
-
-                            </ul>
-                        </li>
-                        <li class="menu nav-item">
-                            <button type="button" class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'users' }"
-                                @click="activeDropdown === 'users' ? (activeDropdown = null) : (activeDropdown = 'users')">
-                                <div class="flex items-center">
-                                    <icon-menu-users class="group-hover:!text-primary shrink-0" />
-
-                                    <span
-                                        class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">系统管理</span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'users' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'users'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/users/profile" @click="toggleMobileMenu">个人设置</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/users/contacts" @click="toggleMobileMenu">账户列表</router-link>
-                                    </li>
-                                </ul>
-                            </vue-collapsible>
-                            <vue-collapsible :isOpen="activeDropdown === 'users'">
-                                <ul class="sub-menu text-gray-500">
-
-
-                                </ul>
-                            </vue-collapsible>
-                        </li>
-                        <!-- <h2 class="py-3 px-7 flex items-center uppercase font-extrabold bg-white-light/30 dark:bg-dark dark:bg-opacity-[0.08] -mx-4 mb-1">
-                            <icon-minus class="w-4 h-5 flex-none hidden" />
-                            <span>{{ $t('user_interface') }}</span>
-                        </h2> -->
-                        <!-- 
-                        <li class="menu nav-item">
-                            <button
-                                type="button"
-                                class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'components' }"
-                                @click="activeDropdown === 'components' ? (activeDropdown = null) : (activeDropdown = 'components')"
-                            >
-                                <div class="flex items-center">
-                                    <icon-menu-components class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{
-                                        $t('components')
-                                    }}</span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown !== 'components' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'components'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/components/tabs" @click="toggleMobileMenu">{{ $t('tabs') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/accordions" @click="toggleMobileMenu">{{ $t('accordions') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/modals" @click="toggleMobileMenu">{{ $t('modals') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/cards" @click="toggleMobileMenu">{{ $t('cards') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/carousel" @click="toggleMobileMenu">{{ $t('carousel') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/countdown" @click="toggleMobileMenu">{{ $t('countdown') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/counter" @click="toggleMobileMenu">{{ $t('counter') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/sweetalert" @click="toggleMobileMenu">{{ $t('sweet_alerts') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/timeline" @click="toggleMobileMenu">{{ $t('timeline') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/notifications" @click="toggleMobileMenu">{{ $t('notifications') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/media-object" @click="toggleMobileMenu">{{ $t('media_object') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/list-group" @click="toggleMobileMenu">{{ $t('list_group') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/pricing-table" @click="toggleMobileMenu">{{ $t('pricing_tables') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/components/lightbox" @click="toggleMobileMenu">{{ $t('lightbox') }}</router-link>
-                                    </li>
-                                </ul>
-                            </vue-collapsible>
-                        </li> -->
-
-                        <!-- <li class="menu nav-item">
-                            <button
-                                type="button"
-                                class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'elements' }"
-                                @click="activeDropdown === 'elements' ? (activeDropdown = null) : (activeDropdown = 'elements')"
-                            >
-                                <div class="flex items-center">
-                                    <icon-menu-elements class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('elements') }}</span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown !== 'elements' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'elements'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/elements/alerts" @click="toggleMobileMenu">{{ $t('alerts') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/avatar" @click="toggleMobileMenu">{{ $t('avatar') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/badges" @click="toggleMobileMenu">{{ $t('badges') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/breadcrumbs" @click="toggleMobileMenu">{{ $t('breadcrumbs') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/buttons" @click="toggleMobileMenu">{{ $t('buttons') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/buttons-group" @click="toggleMobileMenu">{{ $t('button_groups') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/color-library" @click="toggleMobileMenu">{{ $t('color_library') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/dropdown" @click="toggleMobileMenu">{{ $t('dropdown') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/infobox" @click="toggleMobileMenu">{{ $t('infobox') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/jumbotron" @click="toggleMobileMenu">{{ $t('jumbotron') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/loader" @click="toggleMobileMenu">{{ $t('loader') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/pagination" @click="toggleMobileMenu">{{ $t('pagination') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/popovers" @click="toggleMobileMenu">{{ $t('popovers') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/progress-bar" @click="toggleMobileMenu">{{ $t('progress_bar') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/search" @click="toggleMobileMenu">{{ $t('search') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/tooltips" @click="toggleMobileMenu">{{ $t('tooltips') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/treeview" @click="toggleMobileMenu">{{ $t('treeview') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/elements/typography" @click="toggleMobileMenu">{{ $t('typography') }}</router-link>
-                                    </li>
-                                </ul>
-                            </vue-collapsible>
-                        </li> -->
-
-                        <!-- <li class="menu nav-item">
-                            <router-link to="/charts" class="nav-link group" @click="toggleMobileMenu">
-                                <div class="flex items-center">
-                                    <icon-menu-charts class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('charts') }}</span>
-                                </div>
-                            </router-link>
-                        </li>
-
-                        <li class="menu nav-item">
-                            <router-link to="/widgets" class="nav-link group" @click="toggleMobileMenu">
-                                <div class="flex items-center">
-                                    <icon-menu-widgets class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('widgets') }}</span>
-                                </div>
-                            </router-link>
-                        </li>
-
-                        <li class="menu nav-item">
-                            <router-link to="/font-icons" class="nav-link group" @click="toggleMobileMenu">
-                                <div class="flex items-center">
-                                    <icon-menu-font-icons class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{
-                                        $t('font_icons')
-                                    }}</span>
-                                </div>
-                            </router-link>
-                        </li>
-
-                        <li class="menu nav-item">
-                            <router-link to="/dragndrop" class="nav-link group" @click="toggleMobileMenu">
-                                <div class="flex items-center">
-                                    <icon-menu-drag-and-drop class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{
-                                        $t('drag_and_drop')
-                                    }}</span>
-                                </div>
-                            </router-link>
-                        </li>
-
-                        <h2 class="py-3 px-7 flex items-center uppercase font-extrabold bg-white-light/30 dark:bg-dark dark:bg-opacity-[0.08] -mx-4 mb-1">
-                            <icon-minus class="w-4 h-5 flex-none hidden" />
-                            <span>{{ $t('tables_and_forms') }}</span>
-                        </h2>
-
-                        <li class="menu nav-item">
-                            <router-link to="/tables" class="nav-link group" @click="toggleMobileMenu">
-                                <div class="flex items-center">
-                                    <icon-menu-tables class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('tables') }}</span>
-                                </div>
-                            </router-link>
-                        </li> -->
-
-                        <!-- <li class="menu nav-item">
-                            <button
-                                type="button"
-                                class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'datatables' }"
-                                @click="activeDropdown === 'datatables' ? (activeDropdown = null) : (activeDropdown = 'datatables')"
-                            >
-                                <div class="flex items-center">
-                                    <icon-menu-datatables class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{
-                                        $t('datatables')
-                                    }}</span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown !== 'datatables' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'datatables'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/datatables/basic" @click="toggleMobileMenu">{{ $t('basic') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/advanced" @click="toggleMobileMenu">{{ $t('advanced') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/skin" @click="toggleMobileMenu">{{ $t('skin') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/order-sorting" @click="toggleMobileMenu">{{ $t('order_sorting') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/columns-filter" @click="toggleMobileMenu">{{ $t('columns_filter') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/multi-column" @click="toggleMobileMenu">{{ $t('multi_column') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/multiple-tables" @click="toggleMobileMenu">{{ $t('multiple_tables') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/alt-pagination" @click="toggleMobileMenu">{{ $t('alt_pagination') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/checkbox" @click="toggleMobileMenu">{{ $t('checkbox') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/range-search" @click="toggleMobileMenu">{{ $t('range_search') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/export" @click="toggleMobileMenu">{{ $t('export') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/sticky-header" @click="toggleMobileMenu">{{ $t('sticky_header') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/clone-header" @click="toggleMobileMenu">{{ $t('clone_header') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/datatables/column-chooser" @click="toggleMobileMenu">{{ $t('column_chooser') }}</router-link>
-                                    </li>
-                                </ul>
-                            </vue-collapsible>
-                        </li> -->
-
-                        <!-- <li class="menu nav-item">
-                            <button
-                                type="button"
-                                class="nav-link group w-full"
-                                :class="{ active: activeDropdown === 'forms' }"
-                                @click="activeDropdown === 'forms' ? (activeDropdown = null) : (activeDropdown = 'forms')"
-                            >
-                                <div class="flex items-center">
-                                    <icon-menu-forms class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('forms') }}</span>
-                                </div>
-                                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown !== 'forms' }">
-                                    <icon-caret-down />
-                                </div>
-                            </button>
-                            <vue-collapsible :isOpen="activeDropdown === 'forms'">
-                                <ul class="sub-menu text-gray-500">
-                                    <li>
-                                        <router-link to="/forms/basic" @click="toggleMobileMenu">{{ $t('basic') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/input-group" @click="toggleMobileMenu">{{ $t('input_group') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/layouts" @click="toggleMobileMenu">{{ $t('layouts') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/validation" @click="toggleMobileMenu">{{ $t('validation') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/input-mask" @click="toggleMobileMenu">{{ $t('input_mask') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/select2" @click="toggleMobileMenu">{{ $t('select2') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/touchspin" @click="toggleMobileMenu">{{ $t('touchspin') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/checkbox-radio" @click="toggleMobileMenu">{{ $t('checkbox_and_radio') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/switches" @click="toggleMobileMenu">{{ $t('switches') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/wizards" @click="toggleMobileMenu">{{ $t('wizards') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/file-upload" @click="toggleMobileMenu">{{ $t('file_upload') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/quill-editor" @click="toggleMobileMenu">{{ $t('quill_editor') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/markdown-editor" @click="toggleMobileMenu">{{ $t('markdown_editor') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/date-picker" @click="toggleMobileMenu">{{ $t('date_and_range_picker') }}</router-link>
-                                    </li>
-                                    <li>
-                                        <router-link to="/forms/clipboard" @click="toggleMobileMenu">{{ $t('clipboard') }}</router-link>
-                                    </li>
-                                </ul>
-                            </vue-collapsible>
-                        </li> -->
-
-
-
-
-
-
-
-
-                        <!-- <h2 class="py-3 px-7 flex items-center uppercase font-extrabold bg-white-light/30 dark:bg-dark dark:bg-opacity-[0.08] -mx-4 mb-1">
-                            <icon-minus class="w-4 h-5 flex-none hidden" />
-                            <span>{{ $t('supports') }}</span>
-                        </h2>
-
-                        <li class="menu nav-item">
-                            <a href="https://vristo.sbthemes.com" target="_blank" class="nav-link group">
-                                <div class="flex items-center">
-                                    <icon-menu-documentation class="group-hover:!text-primary shrink-0" />
-
-                                    <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">{{
-                                        $t('documentation')
-                                    }}</span>
-                                </div>
-                            </a>
-                        </li> -->
+                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'dashboard' }">
+                  <icon-caret-down />
+                </div>
+              </button>
+              <vue-collapsible :isOpen="activeDropdown === 'dashboard'">
+                <ul class="sub-menu text-gray-500">
+                  <li>
+                    <router-link to="/stock/warehouseList" @click="toggleMobileMenu">{{ $t('warehouseList') }}</router-link>
+                  </li>
+                  <li>
+                    <router-link to="/stock/goodsLend" @click="toggleMobileMenu">{{ $t('goodsLend') }}</router-link>
+                  </li>
+                  <li>
+                    <router-link to="/stock/goodsTransfer" @click="toggleMobileMenu">{{ $t('goodsTransfer') }}</router-link>
+                  </li>
+                </ul>
+              </vue-collapsible>
+            </li>
+            <li class="nav-item">
+              <ul>
+                <li class="menu nav-item">
+                  <button
+                    type="button"
+                    class="nav-link group w-full"
+                    :class="{ active: activeDropdown === 'invoice' }"
+                    @click="activeDropdown === 'invoice' ? (activeDropdown = null) : (activeDropdown = 'invoice')"
+                  >
+                    <div class="flex items-center">
+                      <icon-menu-chat class="group-hover:!text-primary shrink-0" />
+
+                      <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark"> 应用列表 </span>
+                    </div>
+                    <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'invoice' }">
+                      <icon-caret-down />
+                    </div>
+                  </button>
+                  <vue-collapsible :isOpen="activeDropdown === 'invoice'">
+                    <ul class="sub-menu text-gray-500">
+                      <li>
+                        <router-link to="/login" @click="toggleMobileMenu">所有应用</router-link>
+                      </li>
+                      <li>
+                        <router-link to="/apps/custom" @click="toggleMobileMenu">客户列表</router-link>
+                      </li>
+                      <li>
+                        <router-link to="/apps/stock" @click="toggleMobileMenu">仓库列表</router-link>
+                      </li>
+                      <li>
+                        <router-link to="/apps/order" @click="toggleMobileMenu">订单列表</router-link>
+                      </li>
+                      <li>
+                        <router-link to="/apps/goods" @click="toggleMobileMenu">商品列表</router-link>
+                      </li>
                     </ul>
-                </perfect-scrollbar>
-            </div>
-        </nav>
-    </div>
+                  </vue-collapsible>
+                </li>
+              </ul>
+            </li>
+            <li class="menu nav-item">
+              <button
+                type="button"
+                class="nav-link group w-full"
+                :class="{ active: activeDropdown === 'users' }"
+                @click="activeDropdown === 'users' ? (activeDropdown = null) : (activeDropdown = 'users')"
+              >
+                <div class="flex items-center">
+                  <icon-menu-users class="group-hover:!text-primary shrink-0" />
+
+                  <span class="ltr:pl-3 rtl:pr-3 text-black dark:text-[#506690] dark:group-hover:text-white-dark">系统管理</span>
+                </div>
+                <div :class="{ 'rtl:rotate-90 -rotate-90': activeDropdown == 'users' }">
+                  <icon-caret-down />
+                </div>
+              </button>
+              <vue-collapsible :isOpen="activeDropdown === 'users'">
+                <ul class="sub-menu text-gray-500">
+                  <li>
+                    <router-link to="/users/profile" @click="toggleMobileMenu">个人设置</router-link>
+                  </li>
+                  <li>
+                    <router-link to="/users/contacts" @click="toggleMobileMenu">账户列表</router-link>
+                  </li>
+                </ul>
+              </vue-collapsible>
+              <vue-collapsible :isOpen="activeDropdown === 'users'">
+                <ul class="sub-menu text-gray-500"></ul>
+              </vue-collapsible>
+            </li>
+          </ul>
+        </perfect-scrollbar>
+      </div>
+    </nav>
+  </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue';
+import { ref, onMounted, computed } from 'vue';
 import { useStore } from 'vuex';
 import VueCollapsible from 'vue-height-collapsible/vue3';
 
@@ -526,34 +166,40 @@ import IconMenuUsers from '@/components/icon/menu/icon-menu-users.vue';
 import IconMenuPages from '@/components/icon/menu/icon-menu-pages.vue';
 import IconMenuAuthentication from '@/components/icon/menu/icon-menu-authentication.vue';
 import IconMenuDocumentation from '@/components/icon/menu/icon-menu-documentation.vue';
+import SidebarItem from './SidebarItem.vue';
 
 const store = useStore();
 const activeDropdown: any = ref('');
 const subActive: any = ref('');
+const permissionRouters = computed(() => store.getters.permission_routers);
+const sidebar = computed(() => store.getters.sidebar);
+const siteInfo = computed(() => store.getters.siteInfo);
 
+console.log(permissionRouters);
+const isCollapse = computed(() => !sidebar.value.opened);
 onMounted(() => {
-    const selector = document.querySelector('.sidebar ul a[href="' + window.location.pathname + '"]');
-    if (selector) {
-        selector.classList.add('active');
-        const ul: any = selector.closest('ul.sub-menu');
-        if (ul) {
-            let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link') || [];
-            if (ele.length) {
-                ele = ele[0];
-                setTimeout(() => {
-                    ele.click();
-                });
-            }
-        }
+  const selector = document.querySelector('.sidebar ul a[href="' + window.location.pathname + '"]');
+  if (selector) {
+    selector.classList.add('active');
+    const ul: any = selector.closest('ul.sub-menu');
+    if (ul) {
+      let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link') || [];
+      if (ele.length) {
+        ele = ele[0];
+        setTimeout(() => {
+          ele.click();
+        });
+      }
     }
+  }
 });
 
 const toggleMobileMenu = () => {
-    if (window.innerWidth < 1024) {
-        store.dispatch('toggleSidebar');
-    }
+  if (window.innerWidth < 1024) {
+    store.dispatch('toggleSidebar');
+  }
 };
 const toggleSidebars = () => {
-    store.dispatch('toggleSidebar');
+  store.dispatch('toggleSidebar');
 };
 </script>

+ 104 - 0
src/components/layout/SidebarItem.vue

@@ -0,0 +1,104 @@
+<template>
+  <div v-if="!item.hidden && item.children" class="menu-wrapper">
+    <router-link
+      v-if="hasOneShowingChild(item.children, item) && onlyOneChild.value && !onlyOneChild.value.children && !item.alwaysShow"
+      :to="resolvePath(onlyOneChild.value.path)"
+    >
+      <el-menu-item :index="resolvePath(onlyOneChild.value.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
+        <svg-icon v-if="onlyOneChild.value.meta && onlyOneChild.value.meta.icon" :icon-class="onlyOneChild.value.meta.icon"></svg-icon>
+        <span>{{ $t(onlyOneChild.value.meta.title) }}</span>
+      </el-menu-item>
+    </router-link>
+    <el-submenu v-else :index="item.name || item.path">
+      <template #title>
+        <svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
+        <el-tooltip
+          v-if="item.meta && item.meta.title"
+          effect="dark"
+          :content="$t(item.meta.title)"
+          :disabled="$t(item.meta.title).length < 6"
+          placement="right"
+        >
+          <span>{{ $t(item.meta.title) }}</span>
+        </el-tooltip>
+      </template>
+      <template v-for="child in item.children" v-if="child && !child.hidden">
+        <sidebar-item
+          v-if="child.children && child.children.length > 0"
+          :key="child.path"
+          is-nest
+          class="nest-menu"
+          :item="child"
+          :base-path="resolvePath(child.path)"
+        />
+        <router-link v-else :to="resolvePath(child.path)" :key="child.name">
+          <el-menu-item :index="resolvePath(child.path)">
+            <svg-icon v-if="child.meta && child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
+            <el-tooltip
+              v-if="child.meta && child.meta.title"
+              effect="dark"
+              :content="$t(child.meta.title)"
+              :disabled="$t(child.meta.title).length < 6"
+              placement="right"
+            >
+              <span class="item-child">{{ $t(child.meta.title) }}</span>
+            </el-tooltip>
+          </el-menu-item>
+        </router-link>
+      </template>
+    </el-submenu>
+  </div>
+</template>
+  
+  <script lang="ts" setup>
+import { ref } from 'vue';
+import { defineProps } from 'vue';
+import path from 'path-browserify';
+import { useI18n } from 'vue-i18n';
+
+const props = defineProps<{
+  item: any;
+  isNest?: boolean;
+  basePath?: string;
+}>();
+const { t } = useI18n();
+const onlyOneChild = ref<any>(null);
+
+const hasOneShowingChild = (children: any[], item: any) => {
+  const showingChildren = children.filter((child) => {
+    if (child.hidden) {
+      return false;
+    } else {
+      onlyOneChild.value = child;
+      console.log("onlyOneChild:",onlyOneChild);
+      return true;
+    }
+  });
+  if (showingChildren.length === 1) {
+    if (item.redirect === 'dashboard' || item.name === 'dashboard') {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  return false;
+};
+
+const resolvePath = (...paths: string[]) => {
+  return path.resolve(props.basePath || '', ...paths);
+};
+</script>
+  
+<!--   <style scoped lang="scss">
+  span {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: calc(100% - 50px);
+    display: inline-block;
+  }
+  
+  .item-child {
+    width: 100% !important;
+  }
+  </style> -->

+ 1 - 1
src/layouts/app-layout.vue

@@ -55,7 +55,7 @@
                 <!--  END TOP NAVBAR  -->
 
                 <!--  BEGIN CONTENT AREA  -->
-                <div class="p-6 animation">
+                <div class="p-4 animation">
                     <router-view></router-view>
                 </div>
                 <!--  END CONTENT AREA  -->

+ 5 - 1
src/locales/zh.json

@@ -1,5 +1,9 @@
 {
-    "dashboard": "仪表盘",
+    "warehouse": "仓库管理",
+    "warehouseList":"仓库列表",
+    "goodsLend":"库位管理",
+    "goodsTransfer":"库存列表",
+
     "sales": "销售量",
     "analytics": "分析",
     "apps": "应用",

+ 21 - 1
src/main.ts

@@ -66,8 +66,28 @@ import 'element-plus/dist/index.css'
 app.use(ElementPlus)
 
 /* 全局引入 */
-import './permission' 
+import './permission'
 
 import store from './stores';
 app.use(store);
+// 如果您正在使用CDN引入,请删除下面一行。
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+  }
+
+app.config.globalProperties.checkPermission = (value: any): boolean => {
+    if (value && Array.isArray(value) && value.length > 0) {
+        const permissions = store.getters.routersAlls;
+        const permissionDatas = value;
+        const all_permission = '*:*:*';
+        const hasPermission = permissions.some((permission: string) => {
+            return all_permission === permission || permissionDatas.includes(permission);
+        });
+        return !!hasPermission;
+    } else {
+        return false;
+    }
+};
+
 app.mount('#app');

+ 11 - 1
src/router/index.ts

@@ -17,7 +17,17 @@ const asyncRouterMap: RouteRecordRaw[] = [
         redirect: '/dashboard',
         name: 'dashboard',
         children: [
-            { path: '/', component: () => import('@/views/index.vue'), name: 'dashboard', meta: { title: 'home', icon: 'dashboard', layout: 'app'  } }
+            { path: 'dashboard', component: () => import('@/views/index.vue'), name: 'dashboard', meta: { title: 'home', icon: 'dashboard', layout: 'app'  } }
+        ]
+    },
+    {
+        path: '/stock',
+        redirect: '/stock',
+        name: 'stock',
+        children: [
+            { path: 'warehouseList', component: () => import('@/views/stock/warehouseList.vue'), name: 'warehouseList', meta: { title: 'warehouseList',layout: 'app' } },
+            { path: 'goodsLend', component: () => import('@/views/stock/goodsLend.vue'), name: 'goodsLend', meta: { title: 'goodsLend',layout: 'app' } },
+            { path: 'goodsTransfer', component: () => import('@/views/stock/goodsTransfer.vue'), name: 'goodsTransfer', meta: { title: 'goodsTransfer',layout: 'app' } },
         ]
     },
 ]

+ 0 - 1
src/stores/modules/permission.js

@@ -34,7 +34,6 @@ const permission = {
           resolve()
         } else {
           getUserRolesPermissions(role_id).then(response => {
-            console.log('权限',response);
             let accessedRouters = filterRoleRouter(asyncRouterMap, response)
             commit('SET_ROUTERS_ALL', response)
             commit('SET_ROUTERS', accessedRouters)

+ 3 - 1
src/utils/checkToken.js

@@ -28,6 +28,8 @@ export default function checkToken(options) {
   // 登录路径
   let loginPath = Storage.getItem('admin_franchise') ? '/franchise/login' : '/login'
   loginPath = router.resolve(loginPath).href
+   // 获取 store 实例
+   const store = useStore();
   // 返回异步方法
   return new Promise((resolve, reject) => {
     /**
@@ -43,7 +45,7 @@ export default function checkToken(options) {
      * 说明登录已失效、或者cookie有问题,需要重新登录。
      */
     if (!refreshToken || !user) {
-        useStore.dispatch('fedLogoutAction')
+      store.dispatch('fedLogoutAction')
       ElMessageBox.alert('您的登录状态已失效,请重新登录!', '权限错误', {
         type: 'error',
         callback: () => {

+ 2 - 2
src/views/auth/boxed-signin.vue

@@ -17,7 +17,7 @@
           <div class="mx-auto w-full max-w-[440px]">
             <div class="mb-10">
               <h1 class="text-3xl font-extrabold uppercase !leading-snug text-primary md:text-4xl">Sign in</h1>
-              <p class="text-base font-bold leading-normal text-white-dark">欢迎登录V3StudioApp</p>
+           <!--    <p class="text-base font-bold leading-normal text-white-dark">欢迎登录V3StudioApp</p> -->
             </div>
             <!--  <form class="space-y-5 dark:text-white" @submit.prevent="router.push('/')">
                             <div>
@@ -238,7 +238,7 @@ const dispatchLoginAction = (params) => {
         const forward = route.query.forward;
         let query = JSON.parse(JSON.stringify(route.query));
         delete query.forward;
-        router.push({ path: '/', query }).catch(() => {}); //forward ||
+        router.push({ path: 'dashboard', query }).catch(() => {}); //forward ||
       })
       .catch(() => {
         loading.value = false;

+ 2 - 2
src/views/index.vue

@@ -1,13 +1,13 @@
 <template>
     <div>
-        <ul class="flex space-x-2 rtl:space-x-reverse">
+        <!-- <ul class="flex space-x-2 rtl:space-x-reverse">
             <li>
                 <a href="javascript:;" class="text-primary hover:underline">报表统计</a>
             </li>
             <li class="before:content-['/'] ltr:before:mr-2 rtl:before:ml-2">
                 <span>销售情况</span>
             </li>
-        </ul>
+        </ul> -->
 
         <div class="pt-5">
             <div class="grid xl:grid-cols-3 gap-6 mb-6">

+ 434 - 0
src/views/stock/goodsLend.vue

@@ -0,0 +1,434 @@
+<template>
+  <div>
+    <en-table-layout :tableData="tableData.data" :loading="loading" @selection-change="handleSelectionChange">
+      <template #toolbar>
+        <div class="inner-toolbar w-11/12 flex justify-between">
+          <div class="toolbar-btns">
+            <el-button size="mini" v-if="checkPermission(['goodsLend:add'])" type="primary" @click="handleAddGoodsLend">新增</el-button>
+            <el-button
+              type="success"
+              size="mini"
+              v-if="checkPermission(['goodsLend:confirm'])"
+              @click="handleConfirmGoodsLend"
+              :disabled="multipleSelection.length === 0"
+              >打印</el-button
+            >
+          </div>
+          <div class="toolbar-search">
+            <en-table-search @search="searchEvent" @advancedSearch="advancedSearchEvent" placeholder="请输入仓库编号" advanced advancedWidth="465">
+              <template #advanced-content>
+                <el-form ref="advancedForm" :model="advancedForm" label-width="90px">
+                  <el-form-item label="仓库">
+                    <el-cascader
+                      v-model="advancedForm.dept_id"
+                      :options="warehouseList"
+                      :props="{
+                        label: 'name',
+                        value: 'id',
+                        emitPath: false,
+                        checkStrictly: true,
+                      }"
+                      clearable
+                    ></el-cascader>
+                  </el-form-item>
+                  <el-form-item label="状态">
+                    <el-select v-model="advancedForm.status" clearable>
+                      <el-option key="NEW" label="新创建" value="NEW" />
+                      <el-option key="CONFIRMED" label="已确认" value="CONFIRMED" />
+                      <el-option key="RETURNED" label="已归还" value="RETURNED" />
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="时间">
+                    <el-date-picker
+                      v-model="advancedForm.goodsLend_time_range"
+                      type="daterange"
+                      align="center"
+                      :editable="false"
+                      unlink-panels
+                      :clearable="true"
+                      :default-time="['00:00:00', '23:59:59']"
+                      range-separator="-"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                      value-format="timestamp"
+                      :picker-options="{
+                        disabledDate(time) {
+                          return time.getTime() - 1 >= new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate()).getTime() + 86400000 - 1;
+                        },
+                        shortcuts: MixinPickerShortcuts,
+                      }"
+                    >
+                    </el-date-picker>
+                  </el-form-item>
+                </el-form>
+              </template>
+            </en-table-search>
+          </div>
+        </div>
+      </template>
+
+      <template #table-columns>
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="warehouse.sn" label="仓库编号"></el-table-column>
+        <el-table-column prop="warehouse.name" label="仓库名称"></el-table-column>
+        <el-table-column prop="code" label="库位号" />
+        <el-table-column prop="type" label="库位区域">
+          <template #default="scope">{{ statusFilter(scope.row.type) }}</template>
+        </el-table-column>
+        <el-table-column prop="bind_num" label="已绑定SKU数" />
+        <el-table-column label="操作" width="250">
+          <template #default="scope">
+            <el-button size="mini" v-if="checkPermission(['inventoryItem'])" @click="introGoodsLend(scope.row)">查看</el-button>
+            <el-button size="mini" v-if="checkPermission(['bindAreaStock:update'])" @click="AddGoodsLend(scope.row)">绑定商品</el-button>
+            <el-button size="mini" v-if="checkPermission(['goodsLend:edit'])" @click="handleEditGoodsLend(scope.row)">编辑</el-button>
+            <el-button type="success" size="mini" style="margin-top: 5px" v-if="checkPermission(['goodsLend:del'])" @click="handleReturnGoodsLend(scope.row)"
+              >删除</el-button
+            >
+            <el-button type="primary" size="mini" style="margin-top: 5px" @click="handlePrintGoodsLendDialog(scope.row)">打印</el-button>
+          </template>
+        </el-table-column>
+      </template>
+
+      <template #pagination>
+        <el-pagination
+          v-if="tableData"
+          @size-change="handlePageSizeChange"
+          @current-change="handlePageCurrentChange"
+          :current-page="tableData.page_no"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size="tableData.page_size"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="tableData.data_total"
+        >
+        </el-pagination>
+      </template>
+    </en-table-layout>
+
+    <el-dialog title="打印预览" v-model:visible="dialogVisible" width="940px" center @close="handleCancle">
+      <el-button size="mini" type="primary" @click="handlePrintGoodsLend" class="print">打印</el-button>
+      <div id="deliverySheet" style="width: 900px">
+        <div class="tips-t" v-for="(item, index) in codeList" :key="index">
+          <div>
+            <barcode :value="item" height="20" format="CODE128" fontSize="20"></barcode>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+    <el-dialog :title="title" v-model="dialogVisibleForm" width="940px" center @close="addHandleCancle" style="width: 30%;border-radius: 10px">
+      <el-form :model="addLendForm" ref="addLendForm"  :rules="rules" label-width="90px">
+        <el-form-item label="仓库" prop="warehouse_id">
+          <el-select v-model="addLendForm.warehouse_id" clearable @change="selWarehouse">
+            <el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+          <span style="color: red; margin-left: 20px; cursor: pointer; text-decoration: underline" v-if="warehouseList.length == 0" @click="gowareHouse"
+            >新增仓库</span>
+        </el-form-item>
+        <el-form-item label="库位区域" prop="type">
+          <el-select v-model="addLendForm.type" clearable @change="selAreaTableData">
+            <el-option v-for="item in AreaTableData" :key="item.id" :label="item.name" :value="item.code" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="库位号" prop="code">
+          <el-input v-model="addLendForm.code" maxlength="12" placeholder="请输入库位号" style="width: 270px">
+            <template #prepend>{{ warehouseSn }}-{{ AreaTab }}</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="库位说明">
+          <el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" placeholder="请输入内容" v-model="addLendForm.intro"> </el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="addHandleCancle">取 消</el-button>
+        <el-button type="primary" @click="lendCommit">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, watch } from 'vue';
+import * as API_GoodsLend from '@/api/goodsLend';
+import Print from 'print-js';
+import * as API_Setting from '@/api/setting';
+import { useRouter, useRoute } from 'vue-router';
+import QRCode from 'qrcode';
+import EnTableLayout from '@/components/TableLayout/index.vue';
+import EnTableSearch from '@/components/TableSearch/index.vue';
+
+const router = useRouter();
+const route = useRoute();
+
+const warehouseSn = ref('');
+const warehouseList = ref([]);
+const loading = ref(false);
+const params = reactive({
+  page_no: 1,
+  page_size: 10,
+});
+const advancedForm = reactive({});
+const tableData = ref('');
+const dialogVisible = ref(false);
+const multipleSelection = ref([]);
+const goodsLendForm = reactive({});
+const codeList = ref([]);
+const title = ref('');
+const dialogVisibleForm = ref(false);
+const addLendForm = reactive({
+  warehouse_id: '',
+  type: '',
+  code: '',
+  intro: ''
+});
+const rules = reactive({
+  warehouse_id: [{ required: true, message: '请选择仓库', trigger: 'change' }],
+  name: [{ required: true, message: '请选择输入仓库名称', trigger: 'blur' }],
+  code: [{ required: true, message: '请选择输入库位号', trigger: 'blur' }],
+  type: [{ required: true, message: '请选择仓库类型', trigger: 'change' }],
+});
+const typeList = ref([]);
+const AreaTableData = ref([]);
+const AreaTab = ref('');
+
+const GET_GoodsLendList = () => {
+  loading.value = true;
+  API_GoodsLend.query(params)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+onMounted(() => {
+  GET_GoodsLendList();
+  GET_DeptList();
+});
+
+watch(
+  route,
+  (newVal) => {
+    if (newVal.query.status) {
+      params.status = newVal.query.status;
+      advancedForm.status = newVal.query.status;
+    }
+    params.page_no = 1;
+    GET_GoodsLendList();
+  },
+  { immediate: true }
+);
+
+const introGoodsLend = (row) => {
+  router.push({
+    name: 'inventoryItem',
+    params: { id: row.id, type: 'check', code: row.code },
+  });
+};
+
+const AddGoodsLend = (row) => {
+  router.push({
+    name: 'bindAreaStock',
+    params: { id: row.id, warehouse_id: row.warehouse_id, code: row.code },
+  });
+};
+
+const gowareHouse = () => {
+  dialogVisibleForm.value = false;
+  router.push({ name: 'warehouseList' });
+};
+
+const selWarehouse = (val) => {
+  const list = warehouseList.value.filter((el) => el.id === val);
+  let obj = list[0];
+  warehouseSn.value = obj.sn;
+};
+
+const selAreaTableData = (val) => {
+  const list = AreaTableData.value.filter((el) => el.code === val);
+  let obj = list[0];
+  AreaTab.value = obj.code;
+};
+
+const lendCommit = () => {
+  addLendForm.value.code = warehouseSn.value + addLendForm.value.type + '-' + addLendForm.value.code;
+  if (title.value === '新增') {
+    API_GoodsLend.create(addLendForm.value).then(() => {
+      ElMessage.success('添加成功!');
+      dialogVisibleForm.value = false;
+      warehouseSn.value = '';
+      addLendForm.value = {};
+      GET_GoodsLendList();
+    });
+  } else if (title.value === '编辑') {
+    API_GoodsLend.update(addLendForm.value).then(() => {
+      ElMessage.success('修改成功!');
+      dialogVisibleForm.value = false;
+      addLendForm.value = {};
+      GET_GoodsLendList();
+    });
+  }
+};
+
+const addHandleCancle = () => {
+  dialogVisibleForm.value = false;
+};
+
+const handleCancle = () => {
+  dialogVisible.value = false;
+  codeList.value = [];
+};
+
+const creatQrCode = () => {
+  nextTick(() => {
+    codeList.value.forEach((item, index) => {
+      document.getElementById(`codeItem${index}`).innerHTML = '';
+      new QRCode(document.getElementById(`codeItem${index}`), {
+        text: item,
+        width: 600,
+        height: 660,
+        render: 'table',
+        colorDark: '#333333',
+        colorLight: '#ffffff',
+        correctLevel: QRCode.CorrectLevel.H,
+      });
+    });
+  });
+};
+
+const handleEditGoodsLend = (row) => {
+  title.value = '编辑';
+  const str = row.code;
+  warehouseSn.value = str.split('-')[0];
+  AreaTab.value = str.split('-')[1];
+  addLendForm.value = { ...row };
+  addLendForm.value.code = str.split('-').pop();
+  dialogVisibleForm.value = true;
+};
+
+const handleReturnGoodsLend = (row) => {
+  ElMessageBox.confirm('确定要删除吗?', '提示', { type: 'warning' })
+    .then(() => {
+      API_GoodsLend.deletes({ id: row.id }).then(() => {
+        ElMessage.success('删除成功!');
+        GET_GoodsLendList();
+      });
+    })
+    .catch(() => {});
+};
+
+const statusFilter = (val) => {
+  const obj = AreaTableData.value.filter((el) => el.code === val);
+  return obj.length !== 0 ? obj[0].name : '';
+};
+
+const handleConfirmGoodsLend = () => {
+  if (!multipleSelection.value.length) return ElMessage.error('请先选择!');
+  const ids = multipleSelection.value.map((item) => item.code);
+  codeList.value = ids;
+  dialogVisible.value = true;
+  creatQrCode();
+};
+
+const GET_DeptList = () => {
+  API_Setting.getListAll().then((response) => {
+    warehouseList.value = response;
+  });
+  API_Setting.getPage({
+    dictType: 'Warehouse_location_type',
+    page_no: 1,
+    page_size: 1,
+    page_size: 50,
+  }).then((res) => {
+    typeList.value = res.data;
+  });
+  API_GoodsLend.getListAreaType()
+    .then((response) => {
+      loading.value = false;
+      AreaTableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+const handlePrintGoodsLend = () => {
+  Print({
+    printable: 'deliverySheet',
+    type: 'html',
+    targetStyles: ['*'],
+    ignoreElements: ['no-logs', 'goods-image', 'no-btn', 'elementToIgnore'],
+  });
+};
+
+const handleDeleteGoodsLend = () => {
+  if (!multipleSelection.value.length) return ElMessage.error('请先选择!');
+  ElMessageBox.confirm('确定要删除这些借出单吗?', '提示', { type: 'warning' })
+    .then(() => {
+      const ids = multipleSelection.value.map((item) => item.id);
+      API_GoodsLend.deleteGoodsLend(ids).then(() => {
+        ElMessage.success('删除成功!');
+        GET_GoodsLendList();
+      });
+    })
+    .catch(() => {});
+};
+
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val;
+};
+
+const handlePageSizeChange = (size) => {
+  params.page_size = size;
+  GET_GoodsLendList();
+};
+
+const handlePageCurrentChange = (page) => {
+  params.page_no = page;
+  GET_GoodsLendList();
+};
+
+const handleAddGoodsLend = () => {
+  title.value = '新增';
+  dialogVisibleForm.value = true;
+};
+
+const handlePrintGoodsLendDialog = (row) => {
+  dialogVisible.value = true;
+  codeList.value.push(row.code);
+  creatQrCode();
+};
+
+const GET_GoodsLendDetail = (id) => {
+  API_GoodsLend.getGoodsLendInfo(id).then((response) => {
+    goodsLendForm.value = response;
+    if (response.product_list && response.product_list.length) {
+      var str = '';
+      response.product_list.forEach(function (i) {
+        str += i.product_name + '、';
+      });
+    }
+    goodsLendForm.value.illustrate = str;
+  });
+};
+
+const searchEvent = (data) => {
+  params.sn = data;
+  Object.keys(advancedForm).forEach((key) => delete params[key]);
+  params.page_no = 1;
+  GET_GoodsLendList();
+};
+
+const advancedSearchEvent = () => {
+  Object.assign(params, advancedForm);
+  delete params.start_time;
+  delete params.end_time;
+  if (advancedForm.goodsLend_time_range) {
+    params.start_time = parseInt(Number(advancedForm.goodsLend_time_range[0]) / 1000);
+    params.end_time = parseInt(Number(advancedForm.goodsLend_time_range[1]) / 1000);
+  }
+  delete params.goodsLend_time_range;
+  params.page_no = 1;
+  GET_GoodsLendList();
+};
+</script>

+ 289 - 0
src/views/stock/goodsTransfer.vue

@@ -0,0 +1,289 @@
+<template>
+  <div>
+    <div class="search bg-white p-3">
+      <el-form ref="advancedForm" :model="advancedForm" label-width="160px">
+        <el-row>
+          <el-col :span="6">
+            <el-form-item label="仓库">
+              <el-select v-model="advancedForm.warehouse_id" clearable>
+                <el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="商品编号/sku/序列号">
+              <el-input v-model="advancedForm.keyword" clearable placeholder="请输入内容" width="200px"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <el-button @click="advancedSearchEvent"> 搜索 </el-button>
+    </div>
+    <en-table-layout :tableData="tableData.data" ref="table" :loading="loading"
+      @selection-change="handleSelectionChange">
+      <template #toolbar>
+        <div class="inner-toolbar">
+          <div class="toolbar-btns">
+            <el-button size="mini" type="primary" v-if="checkPermission(['updateWarnStock:update'])" @click="handleSubmitGoodsTransfer">设置预警库存</el-button>
+            <el-button size="mini" type="primary" @click="checkExport">导出</el-button>
+          </div>
+        </div>
+      </template>
+
+      <template #table-columns>
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="warehouse_name" label="仓库名称">
+        </el-table-column>
+        <el-table-column label="商品编号" prop="sn">
+        </el-table-column>
+        <el-table-column prop="type" label="类别">
+          <template #default="scope">
+           <!--  {{ scope.row.product_vo.hire_or_sail=='sail' ? '售卖商品' : '租赁商品'  }} -->
+          </template>
+        </el-table-column>
+        <el-table-column prop="goods_name" label="商品名称" />
+        <el-table-column prop="type" label="sku">
+          <template #default="scope">
+            {{ scope.row.product_vo ? scope.row.product_vo.sku : ''  }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="goods_unit" label="单位" />
+        <el-table-column prop="actual_stock" label="实际库存">
+          <template #default="scope">
+            <span :style="Number(scope.row.actual_stock)<Number(scope.row.warning_stock)?'color:red':''">{{ scope.row.actual_stock }}</span> 
+          </template>
+        </el-table-column>
+        <el-table-column prop="usable_stock" label="可用库存" />
+        <el-table-column prop="transit_stock" label="在途库存" />
+        <el-table-column prop="locked_stock" label="冻结库存" />
+        <el-table-column prop="warning_stock" label="预警库存" />
+        <el-table-column label="操作" width="250">
+          <template #default="scope">
+            <el-button type="success1" size="mini" v-if="checkPermission(['goodsTransfer:add']) && scope.row.transit_stock>0"
+              @click="lookTransit(scope.row)">查看在途</el-button>
+          </template>
+        </el-table-column>
+      </template>
+
+      <template #pagination>
+        <el-pagination v-if="tableData" @size-change="handlePageSizeChange"
+          @current-change="handlePageCurrentChange" :current-page="tableData.page_no" :page-sizes="[50, 100]"
+          :page-size="tableData.page_size" layout="total, sizes, prev, pager, next, jumper" :total="tableData.data_total">
+        </el-pagination>
+      </template>
+    </en-table-layout>
+    <el-dialog title="在途明细" v-model:visible="dialogVisible" append-to-body :close-on-click-modal="false"
+      :close-on-press-escape="false">
+      <el-table :data="transitDetails" border class="sku-table">
+        <el-table-column prop="order_sn" label="订单编号" />
+        <el-table-column prop="num" label="在途数量" />
+        <el-table-column prop="end_time" label="返回日期" />
+      </el-table>
+    </el-dialog>
+    <el-dialog title="设置预警数量" v-model:visible="numDialogVisible" append-to-body :close-on-click-modal="false"
+      :close-on-press-escape="false" width="30%">
+      <el-form ref="numAddForm" :model="numAddForm" :rules="numAddFormRule" label-width="160px" inline
+        class="change-form">
+        <el-form-item label="预警数量" prop="num" class="change-form-item">
+          <el-input v-model="numAddForm.num" placeholder="请输入"></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="numDialogVisible = false">取 消</el-button>
+        <el-button @click="submitNum" type="primary">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, watch } from 'vue';
+import * as API_GoodsTransfer from "@/api/goodsTransfer";
+import * as API_Setting from "@/api/setting";
+import * as API_BasicSetting from "@/api/basicSetting";
+import { useStore } from 'vuex';
+import { useRouter, useRoute } from 'vue-router';
+import EnTableLayout from '@/components/TableLayout/index.vue';
+import EnTableSearch from '@/components/TableSearch/index.vue';
+
+const store = useStore();
+const router = useRouter();
+const route = useRoute();
+
+const warehouseList = ref([]);
+const goodstransferType = ref([]);
+const loading = ref(false);
+const params = reactive({
+  page_no: 1,
+  page_size: 50,
+});
+const advancedForm = reactive({});
+const tableData = ref('');
+const multipleSelection = ref([]);
+const GoodsTransferForm = reactive({});
+const dialogVisible = ref(false);
+const transitDetails = ref([]);
+const numDialogVisible = ref(false);
+const numAddForm = reactive({});
+const stockListId = ref([]);
+const numAddFormRule = reactive({
+  num: [ {
+      required: true,
+      message: '请输入预警库存数量',
+      trigger: 'blur'
+    },
+    {
+      validator: (rule, value, callback) => {
+        if (/^(?:[1-9]\d*)$/.test(value) == false) {
+          callback(new Error("请输入正整数"));
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur",
+    },
+  ],
+});
+const selectedRow = ref(null);
+
+const GET_GoodsTransferList = () => {
+  loading.value = true;
+  API_GoodsTransfer.productStock(params)
+    .then((response) => {
+      loading.value = false;
+      tableData.value = response;
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+const GET_WarehouseList = () => {
+  API_BasicSetting.getWarehouseListAll().then((response) => {
+    warehouseList.value = response;
+  });
+};
+
+const GET_DictDataInfo = () => {
+  API_Setting.getDictDataInfo("erp_goodstransfer_type").then((res) => {
+    goodstransferType.value = res;
+  });
+};
+
+const submitNum = () => {
+  numAddForm.value.validate((valid) => {
+    if (valid) {
+      API_GoodsTransfer.updateWarnStock({ stock_id: stockListId.value, warn_num: numAddForm.value.num }).then(res => {
+        numDialogVisible.value = false;
+        numAddForm.value = {};
+        ElMessage.success('预警库存设置成功!');
+        GET_GoodsTransferList();
+      });
+    }
+  });
+};
+
+const handleDelGoodsTransfer = (row) => {
+  ElMessageBox.confirm("确定要删除这些库存吗?", "提示", { type: "warning" })
+    .then(() => {
+      const ids = [row.id];
+      API_GoodsTransfer.deleteGoodsTransfer({ ids }).then(() => {
+        ElMessage.success("删除成功!");
+        GET_GoodsTransferList();
+      });
+    })
+    .catch(() => { });
+};
+
+const lookTransit = (row) => {
+  API_GoodsTransfer.transitDetail(row.id).then(res => {
+    dialogVisible.value = true;
+    transitDetails.value = res;
+  });
+};
+
+const handleSubmitGoodsTransfer = () => {
+  if (stockListId.value.length == 0) return ElMessage.error("请选择");
+  numDialogVisible.value = true;
+};
+
+const handleSelectionChange = (rows) => {
+  stockListId.value = rows.map(el => el.id);
+};
+
+const handlePageSizeChange = (size) => {
+  params.page_size = size;
+  GET_GoodsTransferList();
+};
+
+const handlePageCurrentChange = (page) => {
+  params.page_no = page;
+  GET_GoodsTransferList();
+};
+
+const advancedSearchEvent = () => {
+  Object.assign(params, advancedForm);
+  params.page_no = 1;
+  GET_GoodsTransferList();
+};
+
+onMounted(() => {
+  GET_GoodsTransferList();
+  GET_DictDataInfo();
+  GET_WarehouseList();
+});
+
+watch(route, () => {
+  params.page_no = 1;
+  GET_GoodsTransferList();
+});
+</script>
+
+<!-- <style type="text/scss" lang="scss" scoped>
+.search {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  overflow: hidden;
+  padding: 30px 10px 10px 10px;
+
+  /deep/ .el-form-item {
+    .el-form-item__content {
+      .el-input {
+        width: 200px;
+      }
+    }
+  }
+
+  .el-button {
+    margin-top: -19px;
+    margin-left: 20px;
+  }
+}
+
+.tips-t {
+  display: flex;
+  padding: 10px 0;
+
+  div {
+    flex: 1;
+    text-align: center;
+  }
+}
+
+.tips-f {
+  padding: 20px 0;
+}
+
+.face-image {
+  display: block;
+  width: 50px;
+  height: 50px;
+  margin: 0 auto;
+}
+
+/deep/ .el-dialog__body {
+  padding: 10px 20px;
+}
+</style>
+ -->

+ 247 - 0
src/views/stock/warehouseList.vue

@@ -0,0 +1,247 @@
+<template>
+  <div>
+    <en-table-layout :tableData="tableData.data" :loading="loading">
+      <template #toolbar>
+        <div class="inner-toolbar w-11/12 flex justify-between">
+          <div class="toolbar-btns">
+            <el-button size="mini" v-if="checkPermission(['warehouse:add'])" type="primary" @click="handleAddWarehouse">新增</el-button>
+          </div>
+          <div class="toolbar-search">
+            <en-table-search @search="searchEvent" placeholder="请输入关键字搜索" />
+          </div>
+        </div>
+      </template>
+
+      <template #table-columns>
+        <el-table-column prop="sn" label="仓库编号" />
+        <el-table-column prop="name" label="仓库名称" />
+        <el-table-column prop="phone" label="负责人手机号" />
+        <el-table-column prop="name" label="仓库地址">
+          <template #default="scope">
+            {{ adminName(scope.row.ware_pro_city_area) + scope.row.warehouse_address || '' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" label="仓库说明" />
+        <el-table-column label="操作">
+          <template #default="scope">
+            <el-button size="mini" v-if="checkPermission(['warehouse:update'])" @click="handleEditWarehouse(scope.$index, scope.row)">编辑</el-button>
+          </template>
+        </el-table-column>
+      </template>
+      <template #pagination>
+        <el-pagination
+          v-if="tableData.data && tableData.data.length"
+          @size-change="handlePageSizeChange"
+          @current-change="handlePageCurrentChange"
+          :current-page="tableData.page_no"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size="tableData.page_size"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="tableData.data_total"
+        >
+        </el-pagination>
+      </template>
+    </en-table-layout>
+
+    <el-dialog
+      :title="stockForm.id ? '编辑仓库' : '添加仓库'"
+      v-model="dialogVisible"
+      width="500px"
+      @open="handleDialogOpen"
+      :modal-append-to-body="false"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+    >
+      <el-form :model="stockForm" :rules="stockRules" ref="stockForm" label-width="120px">
+        <el-form-item label="仓库编号" prop="sn">
+          <el-input v-model="stockForm.sn" :minlength="2" :maxlength="4" clearable placeholder="请输入仓库编号"></el-input>
+        </el-form-item>
+        <el-form-item label="仓库名称" prop="name">
+          <el-input v-model="stockForm.name" :minlength="2" :maxlength="20" clearable placeholder="请输入仓库名称"></el-input>
+        </el-form-item>
+        <el-form-item label="发货人姓名">
+          <el-input v-model="stockForm.user_name" :minlength="2" :maxlength="20" clearable placeholder="请输入发货人姓名"></el-input>
+        </el-form-item>
+        <el-form-item label="发货人电话" prop="phone">
+          <el-input v-model="stockForm.phone" :minlength="2" :maxlength="20" clearable placeholder="请输入发货人电话"></el-input>
+        </el-form-item>
+        <el-form-item label="仓库地址">
+          <el-cascader size="large" :options="options" clearable v-model="selectedOptions"> </el-cascader>
+          <span>
+            <el-input style="margin-top: 10px" v-model="stockForm.warehouse_address" placeholder="请输入"></el-input>
+          </span>
+        </el-form-item>
+        <el-form-item label="备注" prop="description">
+          <el-input v-model="stockForm.description" type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" placeholder="请输入备注"></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取 消</el-button>
+          <el-button type="primary" @click="submitWarehouseForm">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue';
+import { useStore } from 'vuex';
+import { ElMessageBox } from 'element-plus';
+import * as API_BasicSetting from '@/api/basicSetting';
+import * as API_Setting from '@/api/setting';
+import * as API_Auth from '@/api/auth';
+import Storage from '@/utils/storage';
+import router from '@/router';
+import EnTableLayout from '@/components/TableLayout/index.vue';
+import EnTableSearch from '@/components/TableSearch/index.vue';
+import { provinceAndCityData, pcTextArr, regionData, pcaTextArr, codeToText } from 'element-china-area-data';
+
+const store = useStore();
+const tableData = ref<any>({});
+const loading = ref(false);
+const dialogVisible = ref(false);
+const stockForm = ref<any>({
+  sn: '',
+  name: '',
+  user_name: '',
+  phone: '',
+  warehouse_address: '',
+  description: '',
+  ware_pro_city_area: []
+});
+const stockRules = ref<any>({
+  sn: [{ required: true, message: '请输入仓库编号', trigger: 'blur' }],
+  name: [{ required: true, message: '请选择输入仓库名称', trigger: 'blur' }],
+  phone: [
+    // { required: true, message: '请输入手机号码!', trigger: 'blur' },
+    // { pattern: /^1[3456789]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }, options: regionData,
+  ],
+});
+const selectedOptions = ref<any[]>([]);
+const options = ref<any[]>(regionData);
+const marketingList = ref<any[]>([]);
+
+const params = ref({
+  data: [],
+  page_no: 1,
+  page_size: 10,
+  keyword: '',
+});
+
+const checkPermission = (permissions: string[]): boolean => {
+  // Implement your permission check logic here
+  return true;
+};
+
+const handleAddWarehouse = () => {
+  stockForm.value = {
+    dept_id: 0,
+    admin_id: store.getters.user.uid,
+  };
+  dialogVisible.value = true;
+};
+
+const handleEditWarehouse = (index: number, row: any) => {
+  stockForm.value = { ...row };
+  selectedOptions.value = row.ware_pro_city_area;
+  dialogVisible.value = true;
+};
+
+const searchEvent = (keyword: string) => {
+  params.value = {
+    ...params.value,
+    keyword: keyword,
+  };
+  params.value.page_no = 1;
+  GET_WarehouseList();
+};
+
+const handlePageSizeChange = (size: number) => {
+  params.value.page_size = size;
+  GET_WarehouseList();
+};
+
+const handlePageCurrentChange = (page: number) => {
+  params.value.page_no = page;
+  GET_WarehouseList();
+};
+
+const handleDialogOpen = () => {
+  setTimeout(() => {
+    stockForm.value.clearValidate();
+  });
+};
+
+const submitWarehouseForm = () => {
+  stockForm.value.validate((valid: boolean) => {
+    if (valid) {
+      const { id } = stockForm.value;
+      const params = { ...stockForm.value };
+      if (!params.real_name) delete params.real_name;
+      params.role_id === '超级仓库' ? (params.role_id = 0) : null;
+      delete params.admin_name;
+      params.admin_id = JSON.parse(JSON.parse(localStorage.getItem('admin_user')!).data).uid;
+      params.ware_pro_city_area = selectedOptions.value.join(',');
+
+      if (id) {
+        API_BasicSetting.editWarehouse(id, params).then(() => {
+          dialogVisible.value = false;
+          store.dispatch('showMessage', { message: '修改成功!', type: 'success' });
+          GET_WarehouseList();
+        });
+      } else {
+        API_BasicSetting.addWarehouse(params).then(() => {
+          dialogVisible.value = false;
+          store.dispatch('showMessage', { message: '添加成功!', type: 'success' });
+          GET_WarehouseList();
+        });
+      }
+    } else {
+      store.dispatch('showMessage', { message: '表单填写有误,请核对!', type: 'error' });
+      return false;
+    }
+  });
+};
+
+const GET_WarehouseList = () => {
+  loading.value = true;
+  API_BasicSetting.warehouse(params.value)
+    .then((response) => {
+      loading.value = false;
+      response.data.map((el: any) => {
+        if (el.ware_pro_city_area !== undefined) {
+          el.ware_pro_city_area = el.ware_pro_city_area.split(',');
+        }
+        return el;
+      });
+      tableData.value = response;
+      console.log(tableData.value);
+    })
+    .catch(() => {
+      loading.value = false;
+    });
+};
+
+const GET_MarketingList = () => {
+  API_BasicSetting.getUserByDept().then((res) => {
+    marketingList.value = res;
+  });
+};
+
+const adminName = (val: string[]) => {
+  let text = '';
+  if (val !== undefined) {
+    text += codeToText[val[0]];
+    text += codeToText[val[1]];
+    text += codeToText[val[2]];
+  }
+  return text;
+};
+
+onMounted(() => {
+  GET_WarehouseList();
+  GET_MarketingList();
+});
+</script>

+ 16 - 1
vite.config.ts

@@ -33,5 +33,20 @@ export default defineConfig({
     define: {
         __API_URL__: JSON.stringify(process.env.VITE_API_URL),
     },
-    
+    // 配置前端服务地址和端口
+    server: {
+        host: '0.0.0.0',
+        port: 5173,
+        // 是否开启 https
+        https: false,
+        // 设置反向代理,跨域
+        proxy: {
+            '/api1': {
+                // 后台地址
+                target: 'http://127.0.0.1:8990/',
+                changeOrigin: true,
+                rewrite: path => path.replace(/^\/api1/, '')
+            },
+        }
+    },
 });

+ 175 - 1
yarn.lock

@@ -1121,6 +1121,11 @@ camelcase-css@^2.0.1:
   resolved "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
   integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
 
+camelcase@^5.0.0:
+  version "5.3.1"
+  resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
 caniuse-api@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@@ -1136,6 +1141,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001629:
   resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78"
   integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==
 
+china-division@^2.7.0:
+  version "2.7.0"
+  resolved "https://registry.npmmirror.com/china-division/-/china-division-2.7.0.tgz#4060a4d243be66c7833dea64a48a4038f3e53e74"
+  integrity sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==
+
 chokidar@^3.5.3:
   version "3.6.0"
   resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -1165,6 +1175,15 @@ clipboard@^2.0.6:
     select "^1.1.2"
     tiny-emitter "^2.0.0"
 
+cliui@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+  integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^6.2.0"
+
 clone@^2.1.1:
   version "2.1.2"
   resolved "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
@@ -1352,6 +1371,11 @@ debug@^4.0.0, debug@^4.3.1, debug@^4.3.4:
   dependencies:
     ms "2.1.2"
 
+decamelize@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+  integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+
 deep-equal@^1.0.1:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz#78a561b7830eef3134c7f6f3a3d6af272a678761"
@@ -1402,6 +1426,11 @@ didyoumean@^1.2.2:
   resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
   integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
 
+dijkstrajs@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
+  integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
+
 dlv@^1.1.3:
   version "1.1.3"
   resolved "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
@@ -1475,6 +1504,13 @@ electron-to-chromium@^1.4.796:
   resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.803.tgz#cf55808a5ee12e2a2778bbe8cdc941ef87c2093b"
   integrity sha512-61H9mLzGOCLLVsnLiRzCbc63uldP0AniRYPV3hbGVtONA1pI7qSGILdbofR7A8TMbOypDocEAjH/e+9k1QIe3g==
 
+element-china-area-data@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.npmmirror.com/element-china-area-data/-/element-china-area-data-6.1.0.tgz#f14b90c0762b21432e097ed5be8423514a0b57e3"
+  integrity sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==
+  dependencies:
+    china-division "^2.7.0"
+
 element-plus@^2.7.5:
   version "2.7.5"
   resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.5.tgz#b6b3101f8c2c61b083c78cae2d8728b93a85039e"
@@ -1513,6 +1549,11 @@ emoji-regex@^9.2.2:
   resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
   integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
 
+encode-utf8@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
+  integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
+
 entities@^2.0.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -1657,6 +1698,14 @@ fill-range@^7.1.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+find-up@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+  dependencies:
+    locate-path "^5.0.0"
+    path-exists "^4.0.0"
+
 flatpickr@^4.6.13:
   version "4.6.13"
   resolved "https://registry.npmmirror.com/flatpickr/-/flatpickr-4.6.13.tgz#8a029548187fd6e0d670908471e43abe9ad18d94"
@@ -1726,6 +1775,11 @@ functions-have-names@^1.2.3:
   resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
   integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
 
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
 get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
   version "1.2.4"
   resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
@@ -1997,6 +2051,13 @@ lines-and-columns@^1.1.6:
   resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
   integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
 
+locate-path@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+  dependencies:
+    p-locate "^4.1.0"
+
 lodash-es@^4.17.21:
   version "4.17.21"
   resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
@@ -2253,11 +2314,40 @@ open@^8.4.0:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
+p-limit@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+  integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+  dependencies:
+    p-try "^2.0.0"
+
+p-locate@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+  dependencies:
+    p-limit "^2.2.0"
+
+p-try@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
 parchment@^1.1.4:
   version "1.1.4"
   resolved "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
   integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
 
+path-browserify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+  integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
+path-exists@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
 path-key@^3.1.0:
   version "3.1.1"
   resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
@@ -2336,6 +2426,11 @@ plist@^3.0.5, plist@^3.1.0:
     base64-js "^1.5.1"
     xmlbuilder "^15.1.1"
 
+pngjs@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
+  integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+
 postcss-calc@^8.2.3:
   version "8.2.4"
   resolved "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
@@ -2622,6 +2717,11 @@ preact@~10.12.1:
   resolved "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21"
   integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==
 
+print-js@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.npmmirror.com/print-js/-/print-js-1.6.0.tgz#692b046cf31992b46afa6c6d8a9db1c69d431d1f"
+  integrity sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==
+
 process@^0.11.1:
   version "0.11.10"
   resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
@@ -2645,6 +2745,21 @@ psl@^1.9.0:
   resolved "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
   integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
 
+qrcode@^1.5.3:
+  version "1.5.3"
+  resolved "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170"
+  integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
+  dependencies:
+    dijkstrajs "^1.0.1"
+    encode-utf8 "^1.0.3"
+    pngjs "^5.0.0"
+    yargs "^15.3.1"
+
+qrcodejs2@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npmmirror.com/qrcodejs2/-/qrcodejs2-0.0.2.tgz#465afe5e39f19facecb932c11f7a186109146ae1"
+  integrity sha512-+Y4HA+cb6qUzdgvI3KML8GYpMFwB24dFwzMkS/yXq6hwtUGNUnZQdUnksrV1XGMc2mid5ROw5SAuY9XhI3ValA==
+
 qs@^6.12.1:
   version "6.12.1"
   resolved "https://registry.npmmirror.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
@@ -2711,6 +2826,16 @@ regexp.prototype.flags@^1.5.1:
     es-errors "^1.3.0"
     set-function-name "^2.0.1"
 
+require-directory@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+  integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
 resolve@^1.1.7, resolve@^1.22.2:
   version "1.22.8"
   resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
@@ -2794,6 +2919,11 @@ semver@^7.3.7:
   resolved "https://registry.npmmirror.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
   integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
 
+set-blocking@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+  integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+
 set-function-length@^1.2.1:
   version "1.2.2"
   resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
@@ -2901,7 +3031,7 @@ stable@^0.1.8:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
-string-width@^4.1.0:
+string-width@^4.1.0, string-width@^4.2.0:
   version "4.2.3"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -3377,6 +3507,11 @@ vuex@^4.1.0:
   dependencies:
     "@vue/devtools-api" "^6.0.0-beta.11"
 
+which-module@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
+  integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
+
 which@^2.0.1:
   version "2.0.2"
   resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
@@ -3393,6 +3528,15 @@ which@^2.0.1:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+  integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@@ -3429,6 +3573,11 @@ xmlbuilder@~11.0.0:
   resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
   integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
 
+y18n@^4.0.0:
+  version "4.0.3"
+  resolved "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
+  integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
+
 yallist@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
@@ -3453,6 +3602,31 @@ yaml@^2.3.4:
   resolved "https://registry.npmmirror.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e"
   integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==
 
+yargs-parser@^18.1.2:
+  version "18.1.3"
+  resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+  integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
+yargs@^15.3.1:
+  version "15.4.1"
+  resolved "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+  integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
+  dependencies:
+    cliui "^6.0.0"
+    decamelize "^1.2.0"
+    find-up "^4.1.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^4.2.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^18.1.2"
+
 yauzl@^2.10.0:
   version "2.10.0"
   resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"