瀏覽代碼

feat: indent用户/登录模块. 项目依赖升级.

peter 4 月之前
父節點
當前提交
acd242487e

+ 6 - 3
.env.development

@@ -1,3 +1,6 @@
-VITE_PO_PATH=https://crm.zoho.com/crm/org810841123/tab/PurchaseOrders/
-VITE_PO_APPEND=''
-VITE_API_PREFIX='/api'
+VITE_CRM_PATH=https://crm.zoho.com/crm/ShowHomePage.do
+VITE_PO_PATH=https://crm.zoho.com/crm/org742735154/tab/PurchaseOrders/
+VITE_PO_APPEND=/canvas/4791186000049921685
+VITE_API_PREFIX='//zohocrmapi.promocollection.com.au'
+VITE_API2_PREFIX='/bpi'
+VITE_APP_OSS_PREFIX = '//promocollection.s3.ap-southeast-2.amazonaws.com'

+ 1 - 0
.env.production

@@ -3,3 +3,4 @@ VITE_PO_PATH=https://crm.zoho.com/crm/org742735154/tab/PurchaseOrders/
 VITE_PO_APPEND=/canvas/4791186000049921685
 VITE_API_PREFIX='//zohocrmapi.promocollection.com.au'
 VITE_API2_PREFIX='/bpi'
+VITE_APP_OSS_PREFIX = '//promocollection.s3.ap-southeast-2.amazonaws.com'

+ 15 - 13
package.json

@@ -13,35 +13,37 @@
     "@element-plus/icons-vue": "^2.1.0",
     "axios": "~0.27.2",
     "dayjs": "^1.11.13",
-    "element-plus": "2.8.7",
+    "element-plus": "2.8.8",
     "html2canvas": "^1.4.1",
+    "js-cookie": "^3.0.5",
     "jspdf": "^2.5.2",
     "lodash": "^4.17.21",
     "lodash.clonedeep": "^4.5.0",
     "lodash.debounce": "^4.0.8",
-    "mathjs": "^13.2.1",
-    "vue": "^3.5.12",
-    "vue-draggable-plus": "^0.5.6",
+    "mathjs": "^13.2.3",
+    "vue": "^3.5.13",
+    "vue-draggable-plus": "^0.6.0",
     "vue-router": "^4.4.5",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@nabla/vite-plugin-eslint": "^2.0.4",
+    "@types/js-cookie": "^3.0.6",
     "@types/lodash.clonedeep": "^4.5.9",
     "@types/lodash.debounce": "^4.0.9",
-    "@typescript-eslint/eslint-plugin": "^7.15.0",
-    "@typescript-eslint/parser": "^7.15.0",
-    "@vitejs/plugin-vue": "^5.1.4",
+    "@typescript-eslint/eslint-plugin": "^7.18.0",
+    "@typescript-eslint/parser": "^7.18.0",
+    "@vitejs/plugin-vue": "^5.2.0",
     "@vue/eslint-config-prettier": "^9.0.0",
     "autoprefixer": "^10.4.20",
-    "eslint": "^8.57.0",
+    "eslint": "^8.57.1",
     "eslint-config-prettier": "^9.1.0",
-    "eslint-plugin-vue": "^9.27.0",
-    "prettier": "^3.3.2",
-    "sass": "^1.77.6",
-    "tailwindcss": "^3.4.14",
+    "eslint-plugin-vue": "^9.31.0",
+    "prettier": "^3.3.3",
+    "sass": "^1.81.0",
+    "tailwindcss": "^3.4.15",
     "typescript": "^5.6.3",
-    "vite": "^5.0.5",
+    "vite": "^5.4.11",
     "vue-tsc": "^2.1.10"
   }
 }

+ 2 - 9
src/App.vue

@@ -1,6 +1,6 @@
 <template>
   <el-config-provider :locale="locale">
-    <router-view v-if="!loading" />
+    <router-view />
   </el-config-provider>
 </template>
 
@@ -12,21 +12,14 @@ export default defineComponent({
 
 <script lang="ts" setup>
 import { defineComponent, ref } from 'vue'
-// import useCommon from './useCommon'
-// import { useRoute, useRouter } from 'vue-router'
 import zhCn from 'element-plus/es/locale/lang/zh-cn'
 import { ElConfigProvider } from 'element-plus'
-// const {} = useCommon()
-// const $route = useRoute()
-// const $router = useRouter()
 
-const loading = ref(true)
-loading.value = false
 const locale = ref(zhCn)
 </script>
 
 <style lang="scss">
-@import '@/assets/css/tailwind.scss';
+@use '@/assets/css/tailwind.scss';
 body {
   position: relative;
   margin: 0;

+ 46 - 0
src/api/user.js

@@ -0,0 +1,46 @@
+import request from '@/utils/axios2'
+
+export default {
+  getUserList: (params) =>
+    request({
+      url: `/indent_admin/list`,
+      method: 'GET',
+      params,
+    }),
+  getUserSelectList: (params) =>
+    request({
+      url: `/indent_admin/selectList`,
+      method: 'GET',
+      params,
+    }),
+  getUserDetail: (params) =>
+    request({
+      url: `/indent_admin/detail/`,
+      method: 'POST',
+      data: params,
+    }),
+  delUser: (params) =>
+    request({
+      url: `/indent_admin/delete`,
+      method: 'GET',
+      params,
+    }),
+  login: (params) =>
+    request({
+      url: `/indent_login/login`,
+      method: 'POST',
+      data: params,
+    }),
+  editUser: (params) =>
+    request({
+      url: `/indent_admin/edit`,
+      method: 'POST',
+      data: params,
+    }),
+  addUser: (params) =>
+    request({
+      url: `/indent_admin/add`,
+      method: 'POST',
+      data: params,
+    }),
+}

+ 1 - 1
src/assets/css/var.module.scss

@@ -1,4 +1,4 @@
-@import './var.scss';
+@use './var.scss';
 :export {
   mainColor: $mainColor;
   subColor: $subColor;

+ 1 - 1
src/assets/css/var.scss

@@ -1,5 +1,5 @@
 $mainColor: #222;
-$textColor: #444;
+// $textColor: #444;
 $subColor: #777;
 $bgColor: #e5e5e5;
 $tableHeaderBgColor: #f8faff;

+ 1 - 1
src/pages/indent-manage/indent/components/calcPrice/components/step1.vue

@@ -254,7 +254,7 @@ const prefix = 'order.indent_calc.'
 }
 </style>
 <style lang="scss" scoped>
-@import '../styles/table.scss';
+@use '../styles/table.scss';
 
 .product-image {
   width: 150px;

+ 1 - 1
src/pages/indent-manage/indent/components/calcPrice/index.vue

@@ -1711,5 +1711,5 @@ let generate = () => {
 }
 </style>
 <style lang="scss" scoped>
-@import './styles/index.scss';
+@use './styles/index.scss';
 </style>

+ 1 - 1
src/pages/indent-manage/indent/components/calcPrice/styles/index.scss

@@ -1,4 +1,4 @@
-@import './table.scss';
+@use './table.scss';
 .compnent-price-calc {
   .step {
     width: 33%;

+ 1 - 1
src/pages/indent-manage/indent/components/info.vue

@@ -1127,7 +1127,7 @@ const formDemo = {
   cert: '',
   notes: '',
 }
-const forms = ref([] as any[])
+const forms = ref<any[]>([])
 const canAddForm = computed(() => {
   return 100 - alreadyHasIndentCount - forms.value.length > 0
 })

+ 19 - 13
src/pages/indent-manage/indent/list.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-indent-list">
+  <div class="page-indent-list py-4 px-2 shadow max-w-[1800px] mx-auto">
     <el-form
       ref="searchForm"
       :inline="true"
@@ -343,13 +343,17 @@
         </template>
       </el-table-column>
     </el-table>
-    <el-pagination
-      v-show="total > 0"
-      v-model:page="pageForm.page"
-      v-model:limit="pageForm.limit"
-      :total="total"
-      @pagination="getList"
-    />
+    <div class="flex justify-end my-4">
+      <el-pagination
+        v-show="total > 0"
+        v-model:current-page="pageForm.page"
+        v-model:page-size="pageForm.limit"
+        v-model:total="total"
+        layout="prev, pager, next, jumper, sizes"
+        @current-change="getList"
+        @size-change="getList"
+      />
+    </div>
     <edit
       v-model:visible="componentEditVisible"
       :indent-data="indentData"
@@ -379,11 +383,13 @@
       style="margin: 5vh auto"
       width="800"
     >
-      <img
-        :src="currentBigImage"
-        style="max-width: 100%; height: auto"
-        alt=""
-      />
+      <div class="flex justify-center">
+        <img
+          :src="currentBigImage"
+          style="max-width: 100%; height: auto"
+          alt=""
+        />
+      </div>
     </el-dialog>
   </div>
 </template>

+ 26 - 0
src/pages/indent-manage/index.vue

@@ -3,10 +3,36 @@
 </template>
 <script lang="ts" setup>
 import { defineComponent, provide } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import Cookie from 'js-cookie'
 defineComponent({
   name: 'IndentManageIndex',
 })
 provide('mediaRegExp', /^(https?:)?\/\/.+(.com.au\/|.com\/)/)
+const $route = useRoute()
+const token = Cookie.get('indent-token')
+
+// 访问非登录页, 未登录状态调整登录页
+if (!token && $route.path !== '/indent-manage/login') {
+  const $router = useRouter()
+  const originQuery = $route.query as {
+    [key: string]: string
+  }
+  const params: any = {
+    origin: encodeURIComponent($route.fullPath),
+  }
+
+  if (originQuery.u?.length && originQuery.p?.length) {
+    params.action = 'autoLogin'
+    params.p = originQuery.p.trim()
+    params.u = originQuery.u.trim()
+  }
+  console.log('未登录重定向')
+  $router.replace({
+    path: '/indent-manage/login',
+    query: params,
+  })
+}
 </script>
 <style>
 .bg-white {

+ 119 - 0
src/pages/indent-manage/login.vue

@@ -0,0 +1,119 @@
+<template>
+  <div
+    style="width: 100%; height: 100vh"
+    class="flex items-center justify-center"
+  >
+    <div
+      class="w-[400px] px-4 py-6 border border-solid border-gray-100 rounded shadow"
+    >
+      <div class="text-center text-gray-2 text-xl font-bold mt-2 mb-8">
+        Indent APP Login
+      </div>
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="rules"
+      >
+        <el-form-item
+          prop="username"
+          label="username: "
+        >
+          <el-input
+            v-model="form.username"
+            :disabled="loading"
+            placeholder="username"
+          ></el-input>
+        </el-form-item>
+        <el-form-item
+          prop="password"
+          label="password: "
+        >
+          <el-input
+            v-model="form.password"
+            type="password"
+            :disabled="loading"
+            placeholder="password"
+          ></el-input>
+        </el-form-item>
+        <el-form-item>
+          <div class="flex justify-center w-[100%]">
+            <el-button
+              :loading="loading"
+              type="primary"
+              @click="tryLogin"
+            >
+              登录
+            </el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref } from 'vue'
+import { ElButton, ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import { useRoute, useRouter } from 'vue-router'
+import Cookie from 'js-cookie'
+import { $t } from '@/i18n/index'
+import userAPI from '@/api/user'
+
+defineComponent({
+  name: 'ComponentIndentUserLogin',
+})
+
+const rules = {
+  username: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  password: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+}
+const formRef = ref<FormInstance>()
+let loading = ref(false)
+let form = ref<any>({
+  username: '',
+  password: '',
+})
+const $route = useRoute()
+const $router = useRouter()
+const tryLogin = () => {
+  if (!formRef.value) {
+    ElMessage.error('error: 表单不存在')
+    return
+  }
+  formRef.value.validate((valid: boolean) => {
+    console.log(valid, 'valid')
+    if (!valid) {
+      ElMessage.error('请检查账户密码输入')
+      return
+    }
+    userAPI.login(form.value).then((res: any) => {
+      console.log(res, 'res')
+      if (res.code !== 1) {
+        ElMessage.error(res.msg || 'login error')
+        return
+      }
+
+      //set token cookie
+      Cookie.set('indent-token', res.result.token)
+      // 登录成功, 有来源的跳转回来源路径, 没有的去indent列表
+      if ($route.query.origin?.length) {
+        $router.replace(decodeURIComponent($route.query.origin as string))
+      } else {
+        $router.replace('/indent-manage/indent/list')
+      }
+    })
+  })
+}
+</script>

+ 153 - 39
src/pages/indent-manage/product/components/edit.vue

@@ -11,50 +11,122 @@
       :close-on-click-modal="false"
       :close-on-press-escape="false"
       :before-close="close"
-      width="550px"
+      width="820px"
     >
       <el-form
         ref="productFormRef"
-        style="width: 500px"
+        style="width: 780px"
         :rules="rules"
         :model="form"
-        label-width="90px"
+        label-width="110px"
       >
-        <el-form-item
-          :label="$t(prefix + 'label_category')"
-          prop="category_id"
-        >
-          <el-cascader
-            v-model="form.category_id"
-            style="width: 410px"
-            :options="categoryData"
-            :props="{ label: 'name', value: 'id' }"
-          ></el-cascader>
-        </el-form-item>
-        <el-form-item
-          :label="$t(prefix + 'label_name_en')"
-          prop="product_name"
-        >
-          <el-input v-model="form.product_name"></el-input>
-        </el-form-item>
-        <el-form-item
-          :label="$t(prefix + 'label_name_cn')"
-          prop="product_name_cn"
-        >
-          <el-input v-model="form.product_name_cn"></el-input>
-        </el-form-item>
-        <el-form-item
-          label="SKU"
-          prop="product_sku"
-        >
-          <el-input v-model="form.product_sku"></el-input>
-        </el-form-item>
-        <el-form-item :label="$t(prefix + 'lable_image_list')">
-          <image-upload
-            v-model:list="imageList"
-            :disable-preview="true"
-          ></image-upload>
-        </el-form-item>
+        <div class="flex justify-between flex-wrap">
+          <el-form-item
+            :label="$t(prefix + 'label_category')"
+            prop="category_id"
+          >
+            <el-cascader
+              v-model="form.category_id"
+              style="width: 410px"
+              :options="categoryData"
+              :props="{ label: 'name', value: 'id' }"
+            ></el-cascader>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix + 'label_name_en')"
+            prop="product_name"
+          >
+            <el-input v-model="form.product_name"></el-input>
+          </el-form-item>
+          <el-form-item
+            :label="$t(prefix + 'label_name_cn')"
+            prop="product_name_cn"
+          >
+            <el-input v-model="form.product_name_cn"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="Primary SKU"
+            prop="product_sku"
+          >
+            <el-input v-model="form.product_sku"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="Primary Code"
+            prop="product_code"
+          >
+            <el-input v-model="form.product_code"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="SKU"
+            prop="sku"
+          >
+            <el-input v-model="form.sku"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="entity"
+            prop="entity"
+          >
+            <el-input v-model="form.entity"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="product_type"
+            prop="product_type"
+          >
+            <el-select v-model="form.product_type">
+              <el-option value="Express"></el-option>
+              <el-option value="indent"></el-option>
+              <el-option value="Stock"></el-option>
+              <el-option value="Other"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Classification"
+            prop="classification"
+          >
+            <el-select v-model="form.classification">
+              <el-option value="Promo Collection"></el-option>
+              <el-option value="Primepac"></el-option>
+              <el-option value="Phoenixexhibit"></el-option>
+              <el-option value="Material"></el-option>
+              <el-option value="Test purpose"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="材质"
+            prop="material"
+          >
+            <el-input v-model="form.material"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="颜色"
+            prop="colour"
+          >
+            <el-input v-model="form.colour"></el-input>
+          </el-form-item>
+          <el-form-item
+            label="尺寸"
+            prop="size"
+          >
+            <el-input v-model="form.size"></el-input>
+          </el-form-item>
+          <el-form-item
+            style="align-items: flex-start;"
+            label="关键词"
+            prop="keywords"
+          >
+            <el-input
+              v-model="form.keywords"
+              type="textarea"
+              :rows="3"
+            ></el-input>
+          </el-form-item>
+          <el-form-item :label="$t(prefix + 'lable_image_list')">
+            <image-upload
+              v-model:list="imageList"
+              :disable-preview="true"
+            ></image-upload>
+          </el-form-item>
+        </div>
       </el-form>
       <template #footer>
         <div class="flex justify-center items-center">
@@ -87,6 +159,8 @@ import {
   ElInput,
   ElNotification,
   ElCascader,
+  ElSelect,
+  ElOption,
 } from 'element-plus'
 import type { FormInstance } from 'element-plus'
 import cloneDeep from 'lodash.clonedeep'
@@ -120,6 +194,7 @@ const form = ref({
   category_id: [],
   images: '',
   status: 1,
+  size: '',
 } as any)
 const rules = {
   product_name: [
@@ -150,6 +225,41 @@ const rules = {
       trigger: 'change',
     },
   ],
+  product_code: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  colour: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  material: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  size: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  sku: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
 }
 
 watch(
@@ -282,4 +392,8 @@ let close = (done = {} as any) => {
 }
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.el-form-item {
+  width: 48%;
+}
+</style>

+ 40 - 12
src/pages/indent-manage/product/list.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-indent-list">
+  <div class="page-indent-list py-4 px-2 shadow max-w-[1800px] mx-auto">
     <el-form
       ref="searchForm"
       :inline="true"
@@ -52,7 +52,7 @@
       </div>
     </el-form>
 
-    <div style="margin-bottom: 12px">
+    <div class="mb-2">
       <el-button
         size="small"
         type="primary"
@@ -85,7 +85,7 @@
               v-if="scope.row[value.field].length"
               style="width: 100%"
               :src="scope.row[value.field][0]"
-              alt=""
+              @click="imgClick(scope.row[value.field][0])"
             />
           </div>
           <div v-else-if="value.field === 'status'">
@@ -143,13 +143,17 @@
         </template>
       </el-table-column>
     </el-table>
-    <el-pagination
-      v-show="total > 0"
-      v-model:page="pageForm.page"
-      v-model:limit="pageForm.limit"
-      :total="total"
-      @pagination="getList"
-    />
+    <div class="flex justify-end my-4">
+      <el-pagination
+        v-show="total > 0"
+        v-model:current-page="pageForm.page"
+        v-model:page-size="pageForm.limit"
+        v-model:total="total"
+        layout="prev, pager, next, jumper, sizes"
+        @current-change="getList"
+        @size-change="getList"
+      />
+    </div>
     <comp-edit
       v-model:visible="componentEditVisible"
       :product-data="dataForEdit"
@@ -167,11 +171,24 @@
       :data-for-examine="dataForEaxmine"
       @update:visible="getList"
     ></comp-examine>
+    <el-dialog
+      v-model="bigImageVisible"
+      style="margin: 5vh auto"
+      width="800"
+    >
+      <div class="flex justify-center">
+        <img
+          :src="currentBigImage"
+          style="max-width: 100%; height: auto"
+          alt=""
+        />
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { defineComponent, ref, inject } from 'vue'
+import { defineComponent, ref, inject, watch } from 'vue'
 import {
   ElButton,
   ElForm,
@@ -185,6 +202,7 @@ import {
   ElNotification,
   ElPagination,
   ElCascader,
+  ElDialog,
 } from 'element-plus'
 import compEdit from './components/edit.vue'
 import compRecord from './components/record.vue'
@@ -275,7 +293,7 @@ const search = () => {
   getList()
 }
 
-const getStatusLabel = (value: any) => {
+const getStatusLabel = (value: number) => {
   const temp = statusList.filter((i: any) => i.value === value)
   return temp.length ? temp[0].label : '-'
 }
@@ -373,6 +391,16 @@ const getCategoryList = () => {
 }
 getList()
 getCategoryList()
+
+const currentBigImage = ref('')
+const bigImageVisible = ref(false)
+const imgClick = (url: string) => {
+  currentBigImage.value = url
+  bigImageVisible.value = true
+}
+watch(bigImageVisible, () => {
+  if (!bigImageVisible.value) currentBigImage.value = ''
+})
 </script>
 
 <style lang="scss" scoped>

+ 272 - 0
src/pages/indent-manage/user/edit.vue

@@ -0,0 +1,272 @@
+<template>
+  <el-dialog
+    v-model="show"
+    :title="visible === 1 ? '新增' : '编辑'"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :before-close="close"
+  >
+    <el-form
+      ref="formRef"
+      v-model:model="form"
+      :rules="rules"
+      label-width="100"
+    >
+      <el-form-item
+        prop="username"
+        label="登录账号"
+      >
+        <el-input
+          v-model="form.username"
+          :disabled="loading"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        prop="status"
+        label="状态"
+      >
+        <el-switch
+          v-model="form.status"
+          :active-value="1"
+          :inactive-value="0"
+        ></el-switch>
+      </el-form-item>
+      <el-form-item
+        prop="name"
+        label="姓名"
+      >
+        <el-input
+          v-model="form.name"
+          :disabled="loading"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        prop="email"
+        label="邮箱地址"
+      >
+        <el-input
+          v-model="form.email"
+          :disabled="loading"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        prop="password"
+        label="密码"
+      >
+        <el-input
+          v-model="form.password"
+          :placeholder="visible === 2 ? '不更改密码直接留空' : ''"
+          :disabled="loading"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        v-if="visible === 1"
+        prop="crm_user_id"
+        label="CRM用户ID"
+      >
+        <el-input
+          v-model="form.crm_user_id"
+          :disabled="loading"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        v-if="visible === 2"
+        prop="admin_ids"
+        label="下属"
+      >
+        <el-select-v2
+          v-model="form.admin_ids"
+          :options="userList"
+          filterable
+          multiple
+        ></el-select-v2>
+      </el-form-item>
+      <!-- <el-form-item> -->
+      <div class="flex justify-center w-[100%] mt-8">
+        <el-button
+          type="primary"
+          @click="onSave"
+        >
+          保存
+        </el-button>
+        <el-button @click="close">取消</el-button>
+      </div>
+      <!-- </el-form-item> -->
+    </el-form>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import {
+  defineComponent,
+  ref,
+  watch,
+  computed,
+  defineProps,
+  defineEmits,
+  nextTick,
+} from 'vue'
+import {
+  ElDialog,
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelectV2,
+  ElSwitch,
+  ElMessage,
+} from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import { CirclePlus } from '@element-plus/icons-vue'
+import debounce from 'lodash.debounce'
+import cloneDeep from 'lodash.clonedeep'
+import { $t } from '@/i18n/index'
+import userAPI from '@/api/user'
+
+defineComponent({
+  name: 'ComponentEditIndentUser',
+})
+const { visible = 0, formData = {} } = defineProps<{
+  visible: number
+  formData: object
+}>()
+const $emit = defineEmits(['update:visible', 'save'])
+let show = ref(false)
+let loading = ref(false)
+const formRef = ref<FormInstance>()
+let form = ref({ password: '' } as any)
+let rules = ref({} as any)
+const defaultRules = {
+  name: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  username: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+  email: [
+    {
+      required: true,
+      message: $t('text_please_input'),
+      trigger: 'change',
+    },
+  ],
+}
+let userList = ref([])
+const getUserSelectListFunc = () => {
+  userAPI
+    .getUserSelectList()
+    .then((res: any) => {
+      if (res.code === 1)
+        userList.value = res.result
+          .map((i: any) => ({
+            value: i.id,
+            label: i.username || i.name,
+          }))
+          .filter((i: any) => i.value !== form.value.id)
+    })
+    .catch(() => console.log('获取用户下拉列表异常'))
+}
+const checkPassword = (rule: any, value: any, cb: any) => {
+  const length = value.trim().length
+  if (length < 6 && length > 0) {
+    return cb(new Error('密码长度须大于6'))
+  }
+  cb()
+}
+watch(
+  () => visible,
+  () => {
+    show.value = visible > 0
+    if (show.value) {
+      form.value = Object.assign({ password: '' }, cloneDeep(formData))
+    }
+
+    if (visible === 1) {
+      rules.value = Object.assign({}, defaultRules, {
+        crm_user_id: [
+          {
+            required: true,
+            message: $t('text_please_input'),
+            trigger: 'change',
+          },
+        ],
+        password: [
+          {
+            required: true,
+            validator: checkPassword,
+            trigger: 'change',
+          },
+        ],
+      })
+    } else if (visible === 2) {
+      rules.value = Object.assign({}, defaultRules, {
+        password: [
+          {
+            required: false,
+            validator: checkPassword,
+            trigger: 'change',
+          },
+        ],
+      })
+      getUserSelectListFunc()
+    }
+    nextTick(() => {
+      formRef.value?.clearValidate()
+    })
+  },
+)
+let close = (done = {} as any) => {
+  $emit('update:visible', 0)
+  if (typeof done === 'function') done()
+}
+const onSave = () => {
+  if (!formRef.value) return
+  formRef.value.validate((valid) => {
+    if (!valid) {
+      ElMessage.error('请检查必填项')
+      return
+    }
+    const f = cloneDeep(form.value)
+    if (f.password.trim().length === 0) {
+      delete f.password
+    }
+    console.log('pass')
+    loading.value = true
+    if (visible === 1) addFunc(f)
+    if (visible === 2) editFunc(f)
+  })
+}
+const editFunc = (f: any) => {
+  userAPI
+    .editUser(f)
+    .then((res: any) => {
+      if (res.code !== 1) {
+        ElMessage.error(res.message || '编辑用户出错')
+        return
+      }
+      $emit('save')
+      close()
+    })
+    .finally(() => (loading.value = false))
+}
+const addFunc = (f: any) => {
+  userAPI
+    .addUser(f)
+    .then((res: any) => {
+      if (res.code !== 1) {
+        ElMessage.error(res.message || '新增用户出错')
+        return
+      }
+      $emit('save')
+      close()
+    })
+    .finally(() => (loading.value = false))
+}
+</script>

+ 222 - 0
src/pages/indent-manage/user/index.vue

@@ -0,0 +1,222 @@
+<template>
+  <div
+    class="page-indent-user-index max-w-[1600px] min-h-[100vh] mx-auto px-4 pt-2"
+  >
+    <div class="text-xl mb-2 text-gray-6">用户管理</div>
+    <div class="flex">
+      <el-form
+        v-model="form"
+        inline
+        @submit="getUserListFunc"
+      >
+        <el-form-item
+          prop="keyword"
+          label="查询: "
+        >
+          <el-input
+            v-model="form.keyword"
+            :disabled="loading"
+          ></el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            :loading="loading"
+            type="primary"
+            @click="getUserListFunc"
+          >
+            查询
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-button
+      type="primary"
+      @click="onAdd"
+    >
+      新增
+    </el-button>
+    <el-table
+      v-loading="loading"
+      :data="list"
+    >
+      <el-table-column
+        label="序号"
+        width="80"
+        type="index"
+      ></el-table-column>
+      <el-table-column
+        label="用户账户"
+        prop="username"
+      ></el-table-column>
+      <el-table-column
+        label="姓名"
+        prop="name"
+      ></el-table-column>
+      <el-table-column
+        label="状态"
+        prop=""
+      >
+        <template #default="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="($e) => changeStatus($e as number, scope.$index)"
+          ></el-switch>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        prop="create_time"
+      ></el-table-column>
+      <el-table-column
+        label="操作"
+        width="160"
+      >
+        <template #default="scope">
+          <el-button
+            size="small"
+            type="primary"
+            @click="onEdit(scope.$index)"
+          >
+            {{ $t('btn_edit') }}
+          </el-button>
+          <el-button
+            size="small"
+            type="danger"
+            @click="onDel(scope.row.id, scope.row.username)"
+          >
+            {{ $t('btn_delete') }}
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <div class="my-4 flex justify-end">
+      <el-pagination
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        v-model:total="total"
+        layout="prev, pager, next, jumper, sizes"
+        :page-sizes="[10, 20, 50, 100]"
+        @current-change="getUserListFunc"
+        @size-change="getUserListFunc"
+      ></el-pagination>
+    </div>
+    <CompEditIndentUser
+      v-model:visible="compEditVisible"
+      :form-data="currentEditItem"
+      @save="getUserListFunc"
+    ></CompEditIndentUser>
+  </div>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref } from 'vue'
+import {
+  ElButton,
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElTable,
+  ElTableColumn,
+  ElPagination,
+  ElSwitch,
+  ElMessage,
+  ElMessageBox,
+} from 'element-plus'
+import CompEditIndentUser from './edit.vue'
+import { $t } from '@/i18n/index'
+import userAPI from '@/api/user'
+import { cloneDeep } from 'lodash'
+
+defineComponent({
+  name: 'ComponentUserIndex',
+})
+let loading = ref(false)
+let form = ref<any>({
+  keyword: '',
+})
+let list = ref<any[]>([])
+
+const pageSize = ref(10)
+const currentPage = ref(1)
+const total = ref(0)
+
+const getUserListFunc = () => {
+  loading.value = true
+  const p: any = {
+    page: currentPage.value,
+    limit: pageSize.value,
+  }
+  if (form.value.keyword) {
+    p.keyword = form.value.keyword
+  }
+  userAPI
+    .getUserList(p)
+    .then((res: any) => {
+      if (res.code !== 1) {
+        return
+      }
+      list.value = res.result.data || []
+      total.value = res.result.total || 0
+      currentPage.value = res.result.current_page || 1
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+getUserListFunc()
+
+const changeStatus = (status: number, index: number) => {
+  const data = cloneDeep(list.value[index])
+  // 兜底操作. 曾经有列表数据返回这个字段, 然后编辑接口又不做过滤, 发回去直接就写表报错.
+  delete data.update_time
+  loading.value = true
+
+  userAPI
+    .editUser(data)
+    .then((res: any) => {
+      if (res.code !== 1) {
+        ElMessage.error('更改失败:' + res.message)
+      }
+      currentPage.value = 1
+
+      getUserListFunc()
+    })
+    .catch(() => {
+      loading.value = false
+    })
+}
+const onDel = (id: string | number, name: string) => {
+  ElMessageBox.confirm(`将删除用户 ${name}, 是否继续?`, '提示', {
+    confirmButtonText: '确定删除',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(() => tryDel(id))
+    .catch(() => console.log(`用户界面取消删除 ${name} 动作`))
+}
+const tryDel = (id: string | number) => {
+  userAPI
+    .delUser({ id: id })
+    .then((res: any) => {
+      if (res.code !== 1) {
+        ElMessage.error('删除失败:' + res.message)
+        return
+      }
+      getUserListFunc()
+    })
+    .catch(() => {
+      loading.value = false
+    })
+}
+const compEditVisible = ref(0)
+const currentEditItem = ref({})
+const onEdit = (num: number) => {
+  currentEditItem.value = cloneDeep(list.value[num])
+  compEditVisible.value = 2
+}
+const onAdd = () => {
+  currentEditItem.value = {}
+  compEditVisible.value = 1
+}
+</script>

+ 13 - 3
src/route.ts

@@ -78,13 +78,23 @@ const router = createRouter({
               component: () => import('@/pages/indent-manage/product/list.vue'),
             },
             {
-              
               path: 'cagegoryList',
               name: 'indent-category-list',
-              component: () => import('@/pages/indent-manage/product/category/index.vue'),
-            }
+              component: () =>
+                import('@/pages/indent-manage/product/category/index.vue'),
+            },
           ],
         },
+        {
+          path: 'user',
+          name: 'indent-user',
+          component: () => import('@/pages/indent-manage/user/index.vue'),
+        },
+        {
+          path: 'login',
+          name: 'indent-user-login',
+          component: () => import('@/pages/indent-manage/login.vue'),
+        },
       ],
     },
     {

+ 4 - 3
src/utils/axios2.js

@@ -1,4 +1,6 @@
 import axios from 'axios'
+import Cookie from 'js-cookie'
+
 const request = axios.create({
   baseURL: import.meta.env.VITE_API2_PREFIX,
 })
@@ -8,9 +10,8 @@ import { ElNotification } from 'element-plus'
 
 request.interceptors.request.use(
   (config) => {
-    config.headers.Authorization =
-      'Bearer ' +
-      'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhZG1pbkAjXHVmZmU1JUAiLCJhdWQiOiIiLCJpYXQiOjE3MzAwODI4MzYsIm5iZiI6MTczMDA4MjgzOSwiZXhwIjoxNzMyNjc0ODM2LCJkYXRhIjp7InVpZCI6MTAxLCJ0eXBlIjoiYWRtaW4ifX0.s2g2vDJa8qAlZ-VL_NhNZNXKI1whIFnXG7ImnbO0nmc'
+    const token = Cookie.get('indent-token')
+    if (token) config.headers.Authorization = `Bearer ${token}`
     return config
   },
   (error) => {

+ 4 - 2
vite.config.ts

@@ -16,7 +16,8 @@ export default defineConfig(({ mode }) => {
           rewrite: (path) => path.replace(/^\/api/, ''),
         },
         '/bpi': {
-          target: 'http://api.promocollection.com.cn:9004',
+          // target: 'http://api.promocollection.com.cn:9004',
+          target: 'http://192.168.10.62:8099',
           changeOrigin: true,
           rewrite: (path) => path.replace(/^\/bpi/, ''),
         },
@@ -31,7 +32,8 @@ export default defineConfig(({ mode }) => {
       devSourcemap: true,
       preprocessorOptions: {
         scss: {
-          additionalData: `@import '@/assets/css/var.scss';`,
+          api: 'modern',
+          additionalData: `@use '@/assets/css/var.scss' as *;`,
         },
       },
     },