Browse Source

1.项目框架构建

GouGengquan 2 months ago
commit
b4c5dd18d6

+ 6 - 0
.editorconfig

@@ -0,0 +1,6 @@
+[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
+charset = utf-8
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 10 - 0
.env.development

@@ -0,0 +1,10 @@
+# 环境变量,哪个环境用的哪个见package.json
+
+NODE_ENV = development
+VITE_NAME='全局环境'
+
+# 接口地址
+VITE_BASE_URL='http://192.168.31.78:8088/api/'
+
+# OA系统地址
+VITE_OA_URL='http://192.168.31.78:9518/'

+ 8 - 0
.env.production

@@ -0,0 +1,8 @@
+// 环境变量,哪个环境用的哪个见package.json
+NODE_ENV = production
+VITE_NAME='全局环境'
+# 接口地址
+VITE_BASE_URL='http://mbs.scdayou.com/api/'
+
+# OA系统地址
+VITE_OA_URL='http://mbs.scdayou.com/admin/'

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 6 - 0
.vscode/extensions.json

@@ -0,0 +1,6 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "dbaeumer.vscode-eslint"
+  ]
+}

+ 35 - 0
README.md

@@ -0,0 +1,35 @@
+# item-management-vant
+
+大友OA系统移动端前端工程
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 17 - 0
eslint.config.mjs

@@ -0,0 +1,17 @@
+import js from '@eslint/js'
+import pluginVue from 'eslint-plugin-vue'
+
+export default [
+  {
+    name: 'app/files-to-lint',
+    files: ['**/*.{js,mjs,jsx,vue}'],
+  },
+
+  {
+    name: 'app/files-to-ignore',
+    ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
+  },
+
+  js.configs.recommended,
+  ...pluginVue.configs['flat/essential'],
+]

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="src\assets\images\dayou_logo.png">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>大友评估主营业务系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 8 - 0
jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

File diff suppressed because it is too large
+ 6541 - 0
package-lock.json


+ 33 - 0
package.json

@@ -0,0 +1,33 @@
+{
+  "name": "item-management-vant",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite --mode development",
+    "build": "vite build --mode production",
+    "preview": "vite preview",
+    "lint": "eslint . --fix"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
+    "axios": "^1.7.7",
+    "element-plus": "^2.8.5",
+    "pinia": "^3.0.2",
+    "pinia-plugin-persistedstate": "^4.3.0",
+    "unplugin-auto-import": "^0.18.3",
+    "unplugin-vue-components": "^0.27.4",
+    "vant": "^4.9.19",
+    "vue": "^3.5.11",
+    "vue-router": "^4.4.5",
+    "js-md5": "^0.7.3",
+    "js-cookie": "^3.0.5"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.12.0",
+    "@vitejs/plugin-vue": "^5.1.4",
+    "eslint": "^9.12.0",
+    "eslint-plugin-vue": "^9.28.0",
+    "vite": "^5.4.8"
+  }
+}

BIN
public/favicon.ico


+ 11 - 0
src/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <div>
+      <RouterView />
+  </div>
+  <div style="height: 5rem;"></div>
+ 
+</template>
+
+<style>
+</style>
+

+ 28 - 0
src/api/user.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request'
+import md5 from 'js-md5'
+
+// 登录
+export function login(params) {
+    return request.post(`user/login`, {
+        account: params.account,
+        pwd: md5(params.pwd),
+    })
+}
+
+// 获取用户信息
+export function userInfo() {
+    return request.get('user/baseInfo')
+}
+
+// 获取用户权限菜单
+export function userMenus(params) {
+    return request.get(`user/privilegesTree`, { params: params })
+}
+
+// 修改密码
+export function modifyPass(params) {
+    return request.put(`/user/password`, {
+        lastPassword: md5(params.lastPassword),
+        newPassword: md5(params.newPassword),
+    })
+}

BIN
src/assets/icons/logout.png


BIN
src/assets/images/dayou_logo.png


BIN
src/assets/images/logo.jpg


BIN
src/assets/images/logo.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

+ 27 - 0
src/assets/main.css

@@ -0,0 +1,27 @@
+#app {
+  width: 100%;
+  margin: 0 auto;
+  font-weight: normal;
+  background-color: #ffffff;
+}
+
+a,
+.green {
+  text-decoration: none;
+  color: hsla(160, 100%, 37%, 1);
+  transition: 0.4s;
+  padding: 3px;
+}
+
+@media (hover: hover) {
+  a:hover {
+    background-color: hsla(160, 100%, 37%, 0.2);
+  }
+}
+
+@media (min-width: 1024px) {
+  body {
+    display: flex;
+    place-items: center;
+  }
+}

File diff suppressed because it is too large
+ 90 - 0
src/components/NavBar.vue


+ 53 - 0
src/components/Tabbar.vue

@@ -0,0 +1,53 @@
+<template>
+  <div>
+    <van-tabbar v-model="active" @change="onChange()">
+      <van-tabbar-item name="home" icon="home-o">首页</van-tabbar-item>
+      <van-tabbar-item name="todo" icon="edit">待办</van-tabbar-item>
+      <van-tabbar-item name="start" icon="guide-o">发起</van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+  
+<script>
+import { mapStores } from 'pinia';
+import { tabStore } from '@/stores/tabStore';
+
+export default {
+  props: {},
+  data() {
+    return {
+      active: 'home',
+    };
+  },
+  computed: {
+    ...mapStores(tabStore),
+  },
+  created() {
+    // 判断缓存中有没有存储激活的页面, 没有默认home
+    if (this.tabStore.tabInfo) {
+      this.active = this.tabStore.tabInfo;
+    }
+  },
+  methods: {
+    // 导航切换
+    onChange() {
+      console.log(this.active);
+      this.tabStore.setTabInfo(this.active);
+      switch (this.active) {
+        case 'home':
+          this.$router.push('/index/home/index');
+          break;
+        case 'todo':
+          this.$router.push('/index/home/todo');
+          break;
+        case 'start':
+          this.$router.push('/index/home/start');
+          break;
+
+        default:
+          break;
+      }
+    },
+  },
+};
+</script>

+ 2 - 0
src/components/index.js

@@ -0,0 +1,2 @@
+export { default as Tabbar } from './Tabbar.vue'
+export { default as NavBar } from './NavBar.vue'

+ 41 - 0
src/layout/components/AppMain.vue

@@ -0,0 +1,41 @@
+<template>
+  <section class="app-main">
+    <router-view v-slot="{ Component }">
+      <transition>
+        <!-- <keep-alive> -->
+        <component :is="Component" />
+        <!-- </keep-alive> -->
+      </transition>
+    </router-view>
+  </section>
+</template>
+  
+  <script>
+export default {
+  name: 'AppMain',
+
+  watch: {
+    $route: {
+      deep: true,
+      immediate: true,
+      handler() {},
+    },
+  },
+
+  data() {
+    return {};
+  },
+  created() {},
+  computed: {},
+
+  methods: {},
+};
+</script>
+  
+<style  scoped>
+.app-main {
+  width: 100%;
+}
+</style>
+  
+  

+ 1 - 0
src/layout/components/index.js

@@ -0,0 +1 @@
+export { default as AppMain } from './AppMain.vue'

+ 30 - 0
src/layout/index.vue

@@ -0,0 +1,30 @@
+<script>
+import { AppMain } from "./components";
+export default{
+    name:'layout',
+    components:{
+        AppMain
+    },
+    created(){
+      console.log("index.page")
+    },
+}
+</script>
+<template>
+    <div class="layout-index">
+        <div>
+            <AppMain></AppMain>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+.fixed-header {
+  position: fixed;
+  top: 0;
+  right: 0;
+  z-index: 9;
+  width: 100%;
+  transition: width 0.28s;
+}
+</style>

+ 24 - 0
src/main.js

@@ -0,0 +1,24 @@
+import './assets/main.css'
+
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+
+import { createPinia } from 'pinia'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
+
+import Vant from 'vant';
+import 'vant/lib/index.css'
+
+const pinia = createPinia()
+// pinia数据持久化
+pinia.use(piniaPluginPersistedstate)
+
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
+const app = createApp(App)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+
+app.use(router).use(pinia).use(Vant).mount('#app')

+ 90 - 0
src/router/index.js

@@ -0,0 +1,90 @@
+import { createWebHashHistory, createRouter } from 'vue-router'
+
+import Layout from '@/layout/index.vue'
+
+// 主页路由
+import HomeIndex from '@/views/home/index.vue'
+import HomeView from '@/views/home/home.vue'
+import TodoView from '@/views/home/todo.vue'
+import StartView from '@/views/home/start.vue'
+
+import LoginView from '@/views/login/index.vue'
+import AssetsView from '@/views/assets/index.vue'
+
+const routes = [
+  // 重定向到首页的路由
+  {
+    path:'/',
+    redirect:'/index/home/index'
+  },
+  {
+    path:'/index',
+    redirect:'/index/home/index'
+  },
+  {
+    path:'/index/home',
+    redirect:'/index/home/index'
+  },
+  // 路由页面配置
+  {
+    path: '/index',
+    component: Layout,
+    name: 'index',
+    children: [
+      {
+        // 首页配置
+        path: 'home',
+        component: HomeIndex,
+        name: 'homeIndex',
+        children: [
+          {
+            path: 'index',
+            component: HomeView,
+            name: 'homeView',
+            meta: {
+              title: '首页'
+            },
+          },
+          {
+            path: 'todo',
+            component: TodoView,
+            name: 'todoView',
+            meta: {
+              title: '待办'
+            },
+          },
+          {
+            path: 'start',
+            component: StartView,
+            name: 'startView',
+            meta: {
+              title: '快速发起'
+            },
+          },
+        ]
+      },
+      // 登录页
+      {
+        path: 'login',
+        component: LoginView,
+        name: 'loginIndex',
+      },
+      // 资产业务
+      {
+        path: 'assets',
+        component: AssetsView,
+        name: 'assetsView',
+        meta: {
+          title: '资产业务'
+        }
+      }
+    ]
+  },
+]
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes,
+})
+
+export default router

+ 19 - 0
src/stores/tabStore.js

@@ -0,0 +1,19 @@
+import { defineStore } from 'pinia'
+
+export const tabStore = defineStore('tab', {
+    state: () => ({
+        tabInfo: null //存储激活页面信息
+    }),
+    actions: {
+        setTabInfo(data) {
+            this.tabInfo = data
+        },
+        removeTabInfo(){
+            this.tabInfo = null;
+        }
+    },
+    persist: {
+        enabled: true,   //开启持久化
+        key: 'tabInfo', //设置存储的key,详情见https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html配置文档
+    },
+});

+ 19 - 0
src/stores/useUserStore.js

@@ -0,0 +1,19 @@
+import { defineStore } from 'pinia'
+
+export const useUserStore = defineStore('user', {
+    state: () => ({
+        userInfo: null //存储用户信息
+    }),
+    actions: {
+        setUserInfo(data) {
+            this.userInfo = data
+        },
+        removeUserInfo(){
+            this.userInfo = null;
+        }
+    },
+    persist: {
+        enabled: true,   //开启持久化
+        key: 'userInfo', //设置存储的key,详情见https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html配置文档
+    },
+});

+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 31 - 0
src/utils/date.js

@@ -0,0 +1,31 @@
+// 计算两个日期之间的天数差
+export function getDaysBetween(dateString1, dateString2) {
+    let startDate = Date.parse(dateString1);
+    let endDate = Date.parse(dateString2);
+    return (endDate - startDate) / (1 * 24 * 60 * 60 * 1000);
+}
+
+// 计算日期字符串往后推算一年的时间并掐头(日减少一天)
+export function processDate(dateStr) {
+    // 将日期字符串转换为 Date 对象
+    const parts = dateStr.match(/(\d+)年(\d+)月(\d+)日/);
+    const year = parseInt(parts[1], 10);
+    const month = parseInt(parts[2], 10) - 1; // JavaScript 中月份是从 0 开始的
+    const day = parseInt(parts[3], 10);
+
+    const date = new Date(year, month, day);
+
+    // 将年份加一
+    date.setFullYear(year + 1);
+    // 减一天
+    date.setDate(day - 1);
+    return date;
+}
+
+// 将 Date 对象转换为 "YYYY年MM月DD日" 格式的字符串
+export function formatDate(date) {
+    const year = date.getFullYear();
+    const month = (date.getMonth() + 1).toString().padStart(2, '0');
+    const day = date.getDate().toString().padStart(2, '0');
+    return `${year}年${month}月${day}日`;
+}

+ 32 - 0
src/utils/file.js

@@ -0,0 +1,32 @@
+const fileUtil = {
+    EncodeGetUrl(url) {
+        let urlArr = url.split('?');
+        let encodeUrl = urlArr[0];
+    
+        if (urlArr.length > 1) {
+          encodeUrl += '?';
+          let paramArr = urlArr[1].split('&');
+          let encodeparamArr = [];
+          paramArr.forEach((item, index) => {
+            let key = item.split('=')[0];
+            let value = item.split('=')[1];
+            encodeparamArr.push(key + '=' + encodeURIComponent(value));
+          });
+    
+          encodeUrl += encodeparamArr.join('&');
+        }
+    
+        return encodeUrl;
+      },
+     
+      download(url) {
+        let downUrl = import.meta.env.VITE_BASE_URL + url;
+        downUrl = this.EncodeGetUrl(downUrl);
+        var a = document.createElement('a');
+        a.href = downUrl;
+        a.target = '_blank';
+        a.click();
+      },
+}
+
+export default fileUtil

+ 28 - 0
src/utils/handlerPaste.js

@@ -0,0 +1,28 @@
+export function imagePasteHandler(e) {
+    var clipboardData = e.clipboardData; // IE
+    if (!clipboardData) {
+        //chrome
+        clipboardData = e.originalEvent.clipboardData;
+    }
+    var items = '';
+    items = (e.clipboardData || window.clipboardData).items;
+    let file = null;
+    if (!items || items.length === 0) {
+        return '当前浏览器不支持粘贴本地图片,请打开图片复制后再粘贴!';
+    }
+    // 搜索剪切板items
+    for (let i = 0; i < items.length; i++) {
+        // 限制上传文件类型
+        if (items[i].type.indexOf('image') !== -1) {
+            file = items[i].getAsFile();
+            break;
+        }
+    }
+    // 判断上传图片尺寸
+    // TODO
+
+    //判断是否超出上传数量
+    // TODO
+
+    return file;
+}

+ 70 - 0
src/utils/request.js

@@ -0,0 +1,70 @@
+import axios from 'axios'
+import { getToken, removeToken } from '@/utils/auth'
+import { showNotify } from 'vant';
+import $router from '@/router'
+
+// create an axios instance
+const service = axios.create({
+  // Vite默认不支持process
+  // baseURL: process.env.VITE_BASE_URL,
+  baseURL: import.meta.env.VITE_BASE_URL,
+  timeout: 50000 // 请求超时
+})
+
+// 请求拦截器
+service.interceptors.request.use(
+  config => {
+    if (getToken()) {
+      // 设置请求带上token
+      config.headers['token'] = getToken()
+    }
+    return config
+  },
+  error => {
+    // 异常情况
+    console.log(error)
+    return Promise.reject(error)
+  }
+)
+
+// 返回拦截器
+service.interceptors.response.use(
+
+  response => {
+    const res = response.data
+
+    // code不等于200的处理
+    if (res.code !== 200) {
+      // code 10004 未登录处理
+      if (res.code === 10004 || res.code === 10005 || res.code === 10008) {
+        showNotify(
+          {
+            type: 'danger',
+            message: res.msg,
+            onClose: () => {
+              // 关闭回调, 跳转到登录
+              $router.push('/index/login');
+            },
+          }
+        );
+      } else {
+        showNotify(
+          {
+            type: 'danger',
+            message: res.msg
+          }
+        );
+      }
+      return res
+    } else {
+      return res
+    }
+  },
+  error => {
+    console.log('err' + error)
+    showNotify({ type: 'danger', message: error.message });
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 16 - 0
src/views/assets/index.vue

@@ -0,0 +1,16 @@
+<template>
+  <div>
+    <van-nav-bar title="资产业务" left-text="返回" left-arrow @click-left="onClickLeft()" />
+    <h1>资产业务</h1>
+  </div>
+</template>
+
+<script>
+export default {
+  methods: {
+    onClickLeft() {
+      history.back();
+    },
+  },
+};
+</script>

File diff suppressed because it is too large
+ 243 - 0
src/views/home/home.vue


+ 59 - 0
src/views/home/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="base">
+    <div>
+      <NavBar></NavBar>
+    </div>
+    <router-view v-slot="{ Component }">
+      <transition>
+        <component :is="Component" />
+      </transition>
+    </router-view>
+    <div>
+      <Tabbar></Tabbar>
+    </div>
+  </div>
+</template>
+
+<script>
+import { userInfo } from '@/api/user';
+import { mapStores } from 'pinia';
+import { useUserStore } from '@/stores/useUserStore';
+import { removeToken } from '@/utils/auth';
+
+import { Tabbar, NavBar } from '@/components';
+
+export default {
+  data() {
+    return {};
+  },
+  components: {
+    Tabbar,
+    NavBar,
+  },
+  computed: {
+    // 非setup语法,需要使用mapStores()才能访问
+    // 直接放在生命周期函数也可以访问,但是似乎会导致持久化失效,没找到解决办法,建议还是用mapStores
+    // 名字会变成id + 'Store' 的形式,比如id本来是user,那么这种方式使用就需要变成this.userStore.xxx
+    ...mapStores(useUserStore),
+  },
+  created() {
+    this.getUserInfo();
+  },
+  methods: {
+    getUserInfo() {
+      userInfo().then((res) => {
+        this.userStore.setUserInfo(res.data);
+      });
+    },
+    goBench(route) {
+      this.$router.push(route);
+    },
+  },
+};
+</script>
+<style scoped>
+.base {
+}
+</style>
+
+

File diff suppressed because it is too large
+ 109 - 0
src/views/home/start.vue


+ 26 - 0
src/views/home/todo.vue

@@ -0,0 +1,26 @@
+<template>
+  <van-tabs v-model:active="activeName">
+    <van-tab title="个贷待办" name="personal">
+      <h1>个贷待办</h1>
+    </van-tab>
+    <van-tab title="大中型待办" name="major">
+      <h1>大中型待办</h1>
+    </van-tab>
+    <van-tab title="资产待办" name="assets">
+      <h1>资产待办</h1>
+    </van-tab>
+  </van-tabs>
+</template>
+
+<script>
+export default { 
+  data() {
+    return {
+      activeName: 'personal',
+    };
+  },
+};
+</script>
+
+<style scoped>
+</style>

+ 205 - 0
src/views/login/index.vue

@@ -0,0 +1,205 @@
+<template>
+  <div style="margin-top: 30%">
+    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="form-control" action autocomplete="on">
+      <img class="title" src="../../assets/images/logo.png" />
+      <div class="input-field">
+        <input ref="account" v-model="loginForm.account" class="input" type="text" />
+        <label class="label" for="input">工号/姓名</label>
+      </div>
+      <div class="input-field">
+        <input ref="pwd" v-model="loginForm.pwd" class="input" type="password" autocomplete="false" />
+        <label class="label" for="input">密码</label>
+      </div>
+      <button class="submit-btn" @click.prevent="handleLogin()">进入</button>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import { login } from '@/api/user';
+import { setToken, getToken } from '@/utils/auth';
+
+export default {
+  name: 'Login',
+  data() {
+    const validateUsername = (rule, value, callback) => {
+      if (!value) {
+        callback(new Error('请输入账号'));
+      } else {
+        callback();
+      }
+    };
+    const validatePassword = (rule, value, callback) => {
+      if (!value) {
+        callback(new Error('请输入密码'));
+      } else {
+        callback();
+      }
+    };
+    return {
+      loadings: false,
+      showboj: true,
+      activeName: 'first',
+      loginForm: {
+        account: '',
+        pwd: '',
+      },
+      loginRules: {
+        account: [
+          {
+            required: true,
+            trigger: 'blur',
+            validator: validateUsername,
+          },
+        ],
+        pwd: [
+          {
+            required: true,
+            trigger: 'blur',
+            validator: validatePassword,
+          },
+        ],
+      },
+      capsTooltip: false,
+      loading: false,
+      showDialog: false,
+      redirect: undefined,
+      otherQuery: {},
+      sysCfg: {},
+    };
+  },
+
+  created() {
+    const that = this;
+    let tok = new RegExp('(^|&)token=([^&]*)(&|$)', 'i');
+    let t = window.location.search.substr(1).match(tok);
+    if (t != null) {
+      let token = decodeURIComponent(t[2]);
+    }
+  },
+  mounted() {
+    if (this.loginForm.account === '') {
+      this.$refs.account.focus();
+    } else if (this.loginForm.pwd === '') {
+      this.$refs.pwd.focus();
+    }
+  },
+  destroyed() {},
+  methods: {
+    // 登录
+    handleLogin() {
+      this.$refs.loginForm.validate((valid) => {
+        if (valid) {
+          this.loading = true;
+          login(this.loginForm).then((res) => {
+            if (res.code === 200) {
+              setToken(res.data.token);
+              // 登录成功,重新跳转到主页
+              this.$router.push(`/index`);
+            }
+          });
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    }
+  },
+};
+</script>
+
+<style scoped>
+.logo {
+  height: 100%;
+  margin-top: 30px;
+}
+
+@keyframes text-shadow-drop-top {
+  0% {
+    text-shadow: 0 0 0 transparent;
+  }
+  100% {
+    text-shadow: 0 -6px 8px rgba(0, 0, 0, 0.35);
+  }
+}
+
+@keyframes focus-in-expand {
+  0% {
+    letter-spacing: -0.5em;
+    filter: blur(12px);
+    opacity: 0;
+  }
+  100% {
+    filter: blur(0);
+    opacity: 1;
+  }
+}
+
+.form-control {
+  background-color: #ffffff;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  padding: 25px;
+}
+.title {
+  width: 100%;
+  height: 100%;
+  margin: 10px;
+}
+.input-field {
+  position: relative;
+  width: 100%;
+}
+
+.input {
+  margin-top: 22px;
+  width: 100%;
+  outline: none;
+  border-radius: 8px;
+  height: 45px;
+  border: 1.5px solid #ecedec;
+  background: transparent;
+}
+.input:focus {
+  border: 1.5px solid #1989FA;
+}
+.input-field .label {
+  position: absolute;
+  top: 25px;
+  left: 15px;
+  color: #ccc;
+  transition: all 0.3s ease;
+  pointer-events: none;
+  z-index: 3;
+}
+.input-field .input:focus ~ .label,
+.input-field .input:valid ~ .label {
+  top: 5px;
+  left: 5px;
+  font-size: 16px;
+  color: #1989FA;
+  background-color: #ffffff;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+.submit-btn {
+  margin-top: 30px;
+  height: 55px;
+  background: #f2f2f2;
+  border-radius: 11px;
+  border: 0;
+  outline: none;
+  color: #ffffff;
+  font-size: 18px;
+  font-weight: 700;
+  background: linear-gradient(180deg, #363636 0%, #1b1b1b 50%, #000000 100%);
+  box-shadow: 0px 0px 0px 0px #ffffff, 0px 0px 0px 0px #000000;
+  transition: all 0.3s cubic-bezier(0.15, 0.83, 0.66, 1);
+  cursor: pointer;
+}
+
+.submit-btn:hover {
+  box-shadow: 0px 0px 0px 2px #ffffff, 0px 0px 0px 4px #0000003a;
+}
+</style>

+ 54 - 0
vite.config.js

@@ -0,0 +1,54 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+
+    AutoImport({
+      resolvers: [ElementPlusResolver()],
+    }),
+
+    Components({
+      resolvers: [ElementPlusResolver()],
+    }),
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    }
+  },
+  server: {
+    host: '192.168.31.78',
+    port: 9519, // 端口
+    proxy: {
+      '/api': { // 请求接口中要替换的标识
+        target: 'http://192.168.31.78:8088', // 代理地址
+        changeOrigin: true, // 是否允许跨域
+        secure: true,
+        rewrite: (path) => path.replace(/^\/api/, '/api') // api标志替换为''
+      },
+    }
+  },
+  base: '/',
+  build: {
+    chunkSizeWarningLimit: 1000,
+    outDir: "dist",
+    assetsDir: "assets", //指定静态资源存放路径
+    // assetsPublicPath:'',
+    sourcemap: true, //是否构建source map 文件
+    terserOptions: {
+      // 生产环境移除console
+      compress: {
+        drop_console: true,
+        drop_debugger: true,
+      },
+    },
+  }
+})