Ver código fonte

登录、组件列表、axios

qianduan 3 meses atrás
pai
commit
fdcd12d922

+ 13 - 0
.env.development

@@ -0,0 +1,13 @@
+# 应用端口
+VITE_APP_PORT = 3000
+
+# 代理前缀
+ENV = '/api'
+
+# 线上接口地址
+# VITE_BASE_URL = http://vapi.youlai.tech
+# 开发接口地址
+VITE_BASE_URL = 'http://192.168.11.77:8888'
+
+# 是否启用 Mock 服务
+VITE_MOCK_DEV_SERVER = false

+ 3 - 0
.env.production

@@ -0,0 +1,3 @@
+# 代理前缀
+ENV  = '/api'
+

+ 4 - 3
src/api/login/index.ts

@@ -1,6 +1,6 @@
-import { request } from "@/utils/service"
+import request from "@/utils/service"
 import type * as Login from "./types/login"
-
+const Url = "http://192.168.11.77:8080/api"
 /** 获取登录验证码 */
 export function getLoginCodeApi() {
   return request<Login.LoginCodeResponseData>({
@@ -12,8 +12,9 @@ export function getLoginCodeApi() {
 /** 登录并返回 Token */
 export function loginApi(data: Login.LoginRequestData) {
   return request<Login.LoginResponseData>({
-    url: "users/login",
+    url: "/login",
     method: "post",
+    baseURL: process.env.NODE_ENV === 'production' ? Url : '/app',
     data
   })
 }

+ 2 - 4
src/api/login/types/login.ts

@@ -1,14 +1,12 @@
 export interface LoginRequestData {
   /** admin 或 editor */
-  username: "admin" | "editor"
+  account: "admin" | "editor"
   /** 密码 */
   password: string
-  /** 验证码 */
-  code: string
 }
 
 export type LoginCodeResponseData = ApiResponseData<string>
 
 export type LoginResponseData = ApiResponseData<{ token: string }>
 
-export type UserInfoResponseData = ApiResponseData<{ username: string; roles: string[] }>
+export type UserInfoResponseData = ApiResponseData<{ account: string; roles: string[] }>

+ 3 - 3
src/api/table/index.ts

@@ -1,4 +1,4 @@
-import { request } from "@/utils/service"
+import request from "@/utils/service"
 import type * as Table from "./types/table"
 
 /** 增 */
@@ -30,8 +30,8 @@ export function updateTableDataApi(data: Table.CreateOrUpdateTableRequestData) {
 /** 查 */
 export function getTableDataApi(params: Table.TableRequestData) {
   return request<Table.TableResponseData>({
-    url: "table",
-    method: "get",
+    url: "/componentList",
+    method: "GET",
     params
   })
 }

+ 4 - 4
src/layouts/components/Logo/index.vue

@@ -1,8 +1,8 @@
 <script lang="ts" setup>
 import { useLayoutMode } from "@/hooks/useLayoutMode"
 import logo from "@/assets/layouts/logo.png?url"
-import logoText1 from "@/assets/layouts/logo-text-1.png?url"
-import logoText2 from "@/assets/layouts/logo-text-2.png?url"
+// import logoText1 from "@/assets/layouts/logo-text-1.png?url"
+// import logoText2 from "@/assets/layouts/logo-text-2.png?url"
 
 interface Props {
   collapse?: boolean
@@ -24,7 +24,7 @@ const { isLeft, isTop } = useLayoutMode()
       <router-link v-else key="expand" to="/">
         <div class="layout-logo-text">
           <img class="latout_card" src="@/assets/layouts/logo.png" />
-          <span :style="{ color: (!isLeft ? '#000' : '#fff') }">宝智达</span>
+          <span :style="{ color: (!isLeft ? '#000' : '#fff') }">面板组件管理</span>
         </div>
       </router-link>
     </transition>
@@ -53,7 +53,7 @@ const { isLeft, isTop } = useLayoutMode()
 
     span {
       margin-left: 10px;
-      font-size: 25px;
+      font-size: 23px;
     }
   }
 }

+ 2 - 5
src/store/modules/user.ts

@@ -18,11 +18,8 @@ export const useUserStore = defineStore("user", () => {
   const settingsStore = useSettingsStore()
 
   /** 登录 */
-  const login = async ({ username, password, code }: LoginRequestData) => {
-    // const { data } = await loginApi({ username, password, code })
-    const data = {
-      token: '12345'
-    }
+  const login = async ({ account, password }: LoginRequestData) => {
+    const { data } = await loginApi({ account, password })
     setToken(data.token)
     token.value = data.token
   }

+ 45 - 116
src/utils/service.ts

@@ -1,122 +1,51 @@
-import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios"
-import { useUserStoreHook } from "@/store/modules/user"
-import { ElMessage } from "element-plus"
-import { get, merge } from "lodash-es"
-import { getToken } from "./cache/cookies"
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+import router from '@/router/index'
+import { getToken } from "@/utils/cache/cookies"
 
-/** 退出登录并强制刷新页面(会重定向到登录页) */
-function logout() {
-  useUserStoreHook().logout()
-  location.reload()
-}
+// 创建axios实例
+const baseUrl = '/api';
+const request = axios.create({
+  baseURL: baseUrl,
+  timeout: 200000, // 请求超时时间
+  headers: { "Content-Type": "application/json;charset=utf-8", 'Authorization': getToken() },
+})
+// request拦截器
+request.interceptors.request.use(config => {
+  return config;
+}, error => {
+  Promise.reject(error);
+});
+
+request.interceptors.response.use(response => {
+  //判断code码
+  return response.data;
+}, error => {
+  return Promise.reject(error);
+});
 
-/** 创建请求实例 */
-function createService() {
-  // 创建一个 axios 实例命名为 service
-  const service = axios.create()
-  // 请求拦截
-  service.interceptors.request.use(
-    (config) => config,
-    // 发送失败
-    (error) => Promise.reject(error)
-  )
-  // 响应拦截(可根据具体业务作出相应的调整)
-  service.interceptors.response.use(
-    (response) => {
-      // apiData 是 api 返回的数据
-      const apiData = response.data
-      // 二进制数据则直接返回
-      const responseType = response.request?.responseType
-      if (responseType === "blob" || responseType === "arraybuffer") return apiData
-      // 这个 code 是和后端约定的业务 code
-      const code = apiData.code
-      // 如果没有 code, 代表这不是项目后端开发的 api
-      if (code === undefined) {
-        ElMessage.error("非本系统的接口")
-        return Promise.reject(new Error("非本系统的接口"))
-      }
-      switch (code) {
-        case 0:
-          // 本系统采用 code === 0 来表示没有业务错误
-          return apiData
-        case 401:
-          // Token 过期时
-          return logout()
-        default:
-          // 不是正确的 code
-          ElMessage.error(apiData.message || "Error")
-          return Promise.reject(new Error("Error"))
-      }
-    },
-    (error) => {
-      // status 是 HTTP 状态码
-      const status = get(error, "response.status")
-      switch (status) {
-        case 400:
-          error.message = "请求错误"
-          break
-        case 401:
-          // Token 过期时
-          logout()
-          break
-        case 403:
-          error.message = "拒绝访问"
-          break
-        case 404:
-          error.message = "请求地址出错"
-          break
-        case 408:
-          error.message = "请求超时"
-          break
-        case 500:
-          error.message = "服务器内部错误"
-          break
-        case 501:
-          error.message = "服务未实现"
-          break
-        case 502:
-          error.message = "网关错误"
-          break
-        case 503:
-          error.message = "服务不可用"
-          break
-        case 504:
-          error.message = "网关超时"
-          break
-        case 505:
-          error.message = "HTTP 版本不受支持"
-          break
-        default:
-          break
-      }
-      ElMessage.error(error.message)
-      return Promise.reject(error)
-    }
-  )
-  return service
+export default request
+
+// 刷新token请求
+function refreshToken() {
+  return axios.post(`${baseUrl}auth/oauth/token`,
+    {
+      'grant_type': 'refresh_token',
+      'refresh_token': sessionStorage.getItem("screen_token")
+    }, { headers: { 'Authorization': 'Basic cGM6cGM=', 'clientType': 'SCREEN', 'Content-type': 'multipart/form-data' } });
 }
 
-/** 创建请求方法 */
-function createRequest(service: AxiosInstance) {
-  return function <T>(config: AxiosRequestConfig): Promise<T> {
-    const token = getToken()
-    const defaultConfig = {
-      headers: {
-        // 携带 Token
-        Authorization: token ? `Bearer ${token}` : undefined,
-        "Content-Type": "application/json"
-      },
-      timeout: 5000,
-      baseURL: import.meta.env.VITE_BASE_API,
-      data: {}
-    }
-    // 将默认配置 defaultConfig 和传入的自定义配置 config 进行合并成为 mergeConfig
-    const mergeConfig = merge(defaultConfig, config)
-    return service(mergeConfig)
-  }
+let subscribers = [];
+
+// 执行队列
+function onAccessTokenFetched() {
+  subscribers.forEach((callback) => {
+    callback();
+  })
+  subscribers = [];
 }
 
-/** 用于网络请求的实例 */
-const service = createService()
-/** 用于网络请求的方法 */
-export const request = createRequest(service)
+// 添加队列
+function addSubscriber(callback) {
+  subscribers.push(callback)
+}

+ 41 - 0
src/utils/status.ts

@@ -0,0 +1,41 @@
+export const showMessage = (status: number | string): string => {
+    let message: string = "";
+    switch (status) {
+        case 400:
+            message = "请求错误(400)";
+            break;
+        case 401:
+            message = "未授权,请重新登录(401)";
+            break;
+        case 403:
+            message = "拒绝访问(403)";
+            break;
+        case 404:
+            message = "请求出错(404)";
+            break;
+        case 408:
+            message = "请求超时(408)";
+            break;
+        case 500:
+            message = "服务器错误(500)";
+            break;
+        case 501:
+            message = "服务未实现(501)";
+            break;
+        case 502:
+            message = "网络错误(502)";
+            break;
+        case 503:
+            message = "服务不可用(503)";
+            break;
+        case 504:
+            message = "网络超时(504)";
+            break;
+        case 505:
+            message = "HTTP版本不受支持(505)";
+            break;
+        default:
+            message = `连接出错(${status})!`;
+    }
+    return `${message},请检查网络或联系管理员!`;
+};

+ 135 - 0
src/views/login/components/identifyingCode.vue

@@ -0,0 +1,135 @@
+<template>
+    <div class="img-verify">
+        <canvas ref="verify" :width="width" :height="height" @click="handleDraw"></canvas>
+    </div>
+</template>
+ 
+<script>
+import { reactive, onMounted, ref, toRefs, defineEmits } from "vue";
+export default {
+    setup(props, context) {
+        const verify = ref(null);
+        const emit = defineEmits(["get-code"]);
+        const state = reactive({
+            pool: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", // 字符串
+            width: 120,
+            height: 40,
+            imgCode: "",
+        });
+        onMounted(() => {
+            // 初始化绘制图片验证码
+            state.imgCode = draw();
+            context.emit("get-code", state.imgCode);
+        });
+        // 点击图片重新绘制
+        const handleDraw = () => {
+            state.imgCode = draw();
+            context.emit("get-code", state.imgCode);
+        };
+
+        // 随机数
+        const randomNum = (min, max) => {
+            return parseInt(Math.random() * (max - min) + min);
+        };
+        // 随机颜色
+        const randomColor = (min, max) => {
+            const r = randomNum(min, max);
+            const g = randomNum(min, max);
+            const b = randomNum(min, max);
+            return `rgb(${r},${g},${b})`;
+        };
+
+        // 绘制图片
+        const draw = () => {
+            // 3.填充背景颜色,背景颜色要浅一点
+            const ctx = verify.value.getContext("2d");
+            // 填充颜色
+            ctx.fillStyle = randomColor(180, 230);
+            // 填充的位置
+            ctx.fillRect(0, 0, state.width, state.height);
+            // 定义paramText
+            let imgCode = "";
+            // 4.随机产生字符串,并且随机旋转
+            for (let i = 0; i < 4; i++) {
+                // 随机的四个字
+                const text = state.pool[randomNum(0, state.pool.length)];
+                imgCode += text;
+                // 随机的字体大小
+                const fontSize = randomNum(18, 40);
+                // 字体随机的旋转角度
+                const deg = randomNum(-30, 30);
+                /*
+                 * 绘制文字并让四个文字在不同的位置显示的思路 :
+                 * 1、定义字体
+                 * 2、定义对齐方式
+                 * 3、填充不同的颜色
+                 * 4、保存当前的状态(以防止以上的状态受影响)
+                 * 5、平移translate()
+                 * 6、旋转 rotate()
+                 * 7、填充文字
+                 * 8、restore出栈
+                 * */
+                ctx.font = fontSize + "px Simhei";
+                ctx.textBaseline = "top";
+                ctx.fillStyle = randomColor(80, 150);
+                /*
+                 * save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。
+                 * 这就允许您临时地改变图像状态,
+                 * 然后,通过调用 restore() 来恢复以前的值。
+                 * save是入栈,restore是出栈。
+                 * 用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
+                 *
+                 * */
+                ctx.save();
+                ctx.translate(30 * i + 15, 15);
+                ctx.rotate((deg * Math.PI) / 180);
+                // fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。
+                // 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。
+                // context.fillText(text,x,y,maxWidth);
+                ctx.fillText(text, -15 + 5, -15);
+                ctx.restore();
+            }
+            // 5.随机产生5条干扰线,干扰线的颜色要浅一点
+            for (let i = 0; i < 5; i++) {
+                ctx.beginPath();
+                ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height));
+                ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height));
+                ctx.strokeStyle = randomColor(180, 230);
+                ctx.closePath();
+                ctx.stroke();
+            }
+            // 6.随机产生40个干扰的小点
+            for (let i = 0; i < 40; i++) {
+                ctx.beginPath();
+                ctx.arc(
+                    randomNum(0, state.width),
+                    randomNum(0, state.height),
+                    1,
+                    0,
+                    2 * Math.PI
+                );
+                ctx.closePath();
+                ctx.fillStyle = randomColor(150, 200);
+                ctx.fill();
+            }
+            return imgCode;
+        };
+
+        return {
+            ...toRefs(state),
+            verify,
+            handleDraw,
+        };
+    },
+};
+</script>
+<style>
+.img-verify {
+    height: 40px;
+    margin: 0 0.1rem;
+}
+
+.img-verify canvas {
+    cursor: pointer;
+}
+</style>

+ 40 - 22
src/views/login/index.vue

@@ -4,11 +4,13 @@ import { useRouter } from "vue-router"
 import { useUserStore } from "@/store/modules/user"
 import { type FormInstance, type FormRules } from "element-plus"
 import { User, Lock, Key, Picture, Loading } from "@element-plus/icons-vue"
-import { getLoginCodeApi } from "@/api/login"
+// import { getLoginCodeApi } from "@/api/login"
 import { type LoginRequestData } from "@/api/login/types/login"
 import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
+import identifyingCode from "./components/identifyingCode.vue"
 import Owl from "./components/Owl.vue"
 import { useFocus } from "./hooks/useFocus"
+import { ElMessage } from 'element-plus'
 
 const router = useRouter()
 const { isFocus, handleBlur, handleFocus } = useFocus()
@@ -22,16 +24,22 @@ const loading = ref(false)
 const codeUrl = ref("")
 /** 登录表单数据 */
 const loginFormData: LoginRequestData = reactive({
-  username: "admin",
-  password: "12345678",
-  code: ""
+  account: "yang",
+  password: "123456",
+  code: '',
 })
+//获取子组件验证码的值
+const canvasCode = ref("");
+const getCode = (val) => {
+  canvasCode.value = val;
+};
+console.log(canvasCode.value.toUpperCase(), 888);
 /** 登录表单校验规则 */
 const loginFormRules: FormRules = {
-  username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
+  account: [{ required: true, message: "请输入用户名", trigger: "blur" }],
   password: [
     { required: true, message: "请输入密码", trigger: "blur" },
-    { min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
+    { min: 6, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
   ],
   code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
 }
@@ -39,19 +47,27 @@ const loginFormRules: FormRules = {
 const handleLogin = () => {
   loginFormRef.value?.validate((valid: boolean, fields) => {
     if (valid) {
-      loading.value = true
-      useUserStore()
-        .login(loginFormData)
-        .then(() => {
-          router.push({ path: "/" })
-        })
-        .catch(() => {
-          createCode()
-          loginFormData.password = ""
-        })
-        .finally(() => {
-          loading.value = false
+      if (loginFormData.code.toUpperCase() != canvasCode.value.toUpperCase()) {
+        loginFormData.code = "";
+        ElMessage({
+          message: '请输入正确的验证码',
+          type: 'warning',
         })
+      } else {
+        loading.value = true
+        useUserStore()
+          .login(loginFormData)
+          .then(() => {
+            router.push({ path: "/" })
+          })
+          .catch(() => {
+            createCode()
+            loginFormData.password = ""
+          })
+          .finally(() => {
+            loading.value = false
+          })
+      }
     } else {
       console.error("表单校验不通过", fields)
     }
@@ -70,6 +86,7 @@ const createCode = () => {
 
 /** 初始化验证码 */
 createCode()
+
 </script>
 
 <template>
@@ -84,8 +101,8 @@ createCode()
       </div>
       <div class="content">
         <el-form ref="loginFormRef" :model="loginFormData" :rules="loginFormRules" @keyup.enter="handleLogin">
-          <el-form-item prop="username">
-            <el-input v-model.trim="loginFormData.username" placeholder="用户名" type="text" tabindex="1" :prefix-icon="User"
+          <el-form-item prop="account">
+            <el-input v-model.trim="loginFormData.account" placeholder="用户名" type="text" tabindex="1" :prefix-icon="User"
               size="large" />
           </el-form-item>
           <el-form-item prop="password">
@@ -96,7 +113,7 @@ createCode()
             <el-input v-model.trim="loginFormData.code" placeholder="验证码" type="text" tabindex="3" :prefix-icon="Key"
               maxlength="7" size="large">
               <template #append>
-                <el-image :src="codeUrl" @click="createCode" draggable="false">
+                <!-- <el-image :src="codeUrl" @click="createCode" draggable="false">
                   <template #placeholder>
                     <el-icon>
                       <Picture />
@@ -107,7 +124,8 @@ createCode()
                       <Loading />
                     </el-icon>
                   </template>
-                </el-image>
+                </el-image> -->
+                <identifyingCode ref="childCode" @get-code="getCode"></identifyingCode>
               </template>
             </el-input>
           </el-form-item>

+ 6 - 16
src/views/table/element-plus/index.vue

@@ -170,25 +170,14 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
         </el-table>
       </div>
       <div class="pager-wrapper">
-        <el-pagination
-          background
-          :layout="paginationData.layout"
-          :page-sizes="paginationData.pageSizes"
-          :total="paginationData.total"
-          :page-size="paginationData.pageSize"
-          :currentPage="paginationData.currentPage"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
+        <el-pagination background :layout="paginationData.layout" :page-sizes="paginationData.pageSizes"
+          :total="paginationData.total" :page-size="paginationData.pageSize" :currentPage="paginationData.currentPage"
+          @size-change="handleSizeChange" @current-change="handleCurrentChange" />
       </div>
     </el-card>
     <!-- 新增/修改 -->
-    <el-dialog
-      v-model="dialogVisible"
-      :title="formData.id === undefined ? '新增用户' : '修改用户'"
-      @closed="resetForm"
-      width="30%"
-    >
+    <el-dialog v-model="dialogVisible" :title="formData.id === undefined ? '新增用户' : '修改用户'" @closed="resetForm"
+      width="30%">
       <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" label-position="left">
         <el-form-item prop="username" label="用户名">
           <el-input v-model="formData.username" placeholder="请输入" />
@@ -208,6 +197,7 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
 <style lang="scss" scoped>
 .search-wrapper {
   margin-bottom: 20px;
+
   :deep(.el-card__body) {
     padding-bottom: 2px;
   }

+ 33 - 13
vite.config.ts

@@ -1,16 +1,36 @@
-import { fileURLToPath, URL } from 'node:url'
-
-import { defineConfig } from 'vite'
+import { defineConfig, loadEnv } from 'vite'
 import vue from '@vitejs/plugin-vue'
-
 // https://vitejs.dev/config/
-export default defineConfig({
-  plugins: [
-    vue(),
-  ],
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
+import { resolve } from "path";
+const pathSrc = resolve(__dirname, "src");
+export default defineConfig(({ mode }) => {
+  const config = loadEnv(mode, './')
+  return {
+    plugins: [
+      vue(),
+    ],
+    server: {
+      proxy: {
+        '/api': {
+          target: 'http://192.168.11.77:8888',
+          changeOrigin: true,
+          rewrite: (path) => {
+            return path.replace(/\/api/, '/api')
+          }
+        },
+        '/app': {
+          target: 'http://192.168.11.77:8080',
+          changeOrigin: true,
+          rewrite: (path) => {
+            return path.replace(/\/app/, '/api')
+          }
+        }
+      }
+    },
+    resolve: {
+      alias: {
+        "@": pathSrc,
+      },
+    },
   }
-})
+})