This commit is contained in:
cxc
2023-02-14 13:32:03 +08:00
parent 6ae8288f1a
commit d94eb375fe
27 changed files with 1665 additions and 67 deletions

View File

@ -1,15 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier'
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier",
],
parserOptions: {
ecmaVersion: 'latest'
}
}
ecmaVersion: "latest",
},
};

View File

@ -1 +1 @@
{}
{}

View File

@ -13,8 +13,8 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration

5
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

34
components.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<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.ts"></script>
</body>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<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.ts"></script>
</body>
</html>

View File

@ -14,13 +14,18 @@
"@element-plus/icons-vue": "^2.0.10",
"axios": "^1.3.2",
"element-plus": "^2.2.30",
"js-cookie": "^3.0.1",
"js-md5": "^0.7.3",
"jsencrypt": "^3.3.1",
"modern-normalize": "^1.1.0",
"pinia": "^2.0.30",
"qrcode.vue": "^3.3.3",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@types/js-cookie": "^3.0.2",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
@ -35,6 +40,9 @@
"sass": "^1.58.0",
"tailwindcss": "^3.2.6",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.14.2",
"unplugin-icons": "^0.15.2",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}

948
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
// export default defineComponent({
// components: { Gen_short_link }
//
// });
// @ts-ignore
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
</script>
<template>
<!-- <GenShortLink class="w-full h-full"></GenShortLink>-->
<RouterView />
<el-config-provider :locale="zhCn">
<RouterView />
</el-config-provider>
</template>
<style scoped>

42
src/api/login.ts Normal file
View File

@ -0,0 +1,42 @@
import request from "@/utils/request";
// @ts-ignore
import md5 from "js-md5";
// 登录方法
export function login(
username: string,
password: string,
code: string,
uuid: string
) {
const data = {
username,
password: md5(password),
code,
uuid,
};
return request({
url: "/login",
headers: {
isToken: false,
},
method: "post",
data: data,
});
}
// 获取用户详细信息
export function getInfo() {
return request({
url: "/getInfo",
method: "get",
});
}
// 退出方法
export function logout() {
return request({
url: "/logout",
method: "post",
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

View File

@ -1,24 +1,26 @@
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";
import router from "./router";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "./assets/main.css";
import "modern-normalize/modern-normalize.css";
import "./permission"; // permission control
// import "@/assets/iconfont/iconfont.js";
import "@/assets/iconfont/iconfont.css";
// @ts-ignore
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
// import zhCn from "element-plus/dist/locale/zh-cn.mjs";
//
// import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const app = createApp(App);
app.use(store);
app.use(router);
app.use(ElementPlus, {
locale: zhCn,
});
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
// app.use(ElementPlus, {
// locale: zhCn,
// });
// for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// app.component(key, component);
// }
app.mount("#app");

40
src/permission.ts Normal file
View File

@ -0,0 +1,40 @@
import { getToken } from "@/utils/auth";
import router from "@/router";
import { useUserStore } from "@/store/user";
const whiteList = ["/login"];
// const userStore = useUserStore();
router.beforeEach((to, from, next) => {
if (getToken()) {
if (to.path === "/login") {
next({ path: "/" });
// NProgress.done()
} else {
if (useUserStore().roles.length === 0) {
useUserStore()
.getInfo()
.then(() => {
next({ ...to, replace: true });
})
.catch((err) => {
console.log(err);
useUserStore()
.logOut()
.then(() => {
next({ path: "/" });
});
});
} else {
next();
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next();
} else {
next(`/login?redirect=${to.fullPath}`); // 否则全部重定向到登录页
}
}
});

View File

@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from "vue-router";
import GenShortLink from "@/views/GenShortLink.vue";
import AccessRecords from "@/views/AccessRecords.vue";
import Login from "@/views/Login.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -15,6 +16,11 @@ const router = createRouter({
name: "AccessRecords",
component: AccessRecords,
},
{
path: "/login",
name: "Login",
component: Login,
},
],
});

5
src/store/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { createPinia } from "pinia";
const store = createPinia();
export default store;

73
src/store/user.ts Normal file
View File

@ -0,0 +1,73 @@
import { defineStore } from "pinia";
import { getToken, removeToken, setToken } from "@/utils/auth";
import { getInfo, login, logout } from "@/api/login";
export const useUserStore = defineStore("user", {
state: () => {
return {
token: getToken(),
name: "",
avatar: "",
roles: [],
permissions: [],
};
},
actions: {
// 登录
login(userInfo: any) {
const username = userInfo.username.trim();
const password = userInfo.password;
const code = userInfo.code;
const uuid = userInfo.uuid;
return new Promise((resolve, reject) => {
login(username, password, code, uuid)
.then((res: any) => {
setToken(res.data.token);
this.token = res.data.token;
resolve(null);
})
.catch((error: any) => {
reject(error);
});
});
},
// 退出系统
logOut() {
return new Promise<void>((resolve, reject) => {
logout()
.then(() => {
this.token = "";
removeToken();
resolve();
})
.catch((error: any) => {
reject(error);
});
});
}, // 获取用户信息
getInfo() {
return new Promise((resolve, reject) => {
getInfo()
.then((res: any) => {
const user = res.data.user;
// const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
if (res.data.roles && res.data.roles.length > 0) {
// 验证返回的roles是否是一个非空数组
this.roles = res.data.roles;
this.permissions = res.data.permissions;
} else {
// @ts-ignore
this.roles = ["ROLE_DEFAULT"];
}
this.name = user.userName;
// this.avatar = avatar;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
},
},
});

View File

@ -5,4 +5,5 @@ export interface ShortLink {
urlPrefix?: string;
urlSuffix?: string;
validityTime?: string;
status?: boolean;
}

16
src/utils/auth.ts Normal file
View File

@ -0,0 +1,16 @@
// @ts-ignore
import Cookies from "js-cookie";
const TokenKey = "Admin-Token";
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token: string) {
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}

31
src/utils/jsencrypt.ts Normal file
View File

@ -0,0 +1,31 @@
// @ts-ignore
import JSEncrypt from "jsencrypt/bin/jsencrypt.min";
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey =
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n" +
"nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==";
const privateKey =
"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n" +
"7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n" +
"PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n" +
"kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n" +
"cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n" +
"DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n" +
"YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n" +
"UP8iWi1Qw0Y=";
// 加密
export function encrypt(txt: string) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey); // 设置公钥
return encryptor.encrypt(txt); // 对数据进行加密
}
// 解密
export function decrypt(txt: string) {
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey); // 设置私钥
return encryptor.decrypt(txt); // 对数据进行解密
}

View File

@ -1,6 +1,7 @@
import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";
import axios from "axios";
import { ElMessage, ElNotification } from "element-plus";
import { errorCode } from "@/utils/errorCode";
import { getToken } from "@/utils/auth";
// 创建axios实例
const service = axios.create({
@ -11,6 +12,17 @@ const service = axios.create({
timeout: 100000,
});
// @ts-ignore
service.interceptors.request.use((config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
if (getToken() && !isToken) {
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config;
});
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态

View File

@ -1,8 +1,8 @@
<template>
<div id="app-container">
<el-button size="small" @click="back" type="primary" plain icon="back"
>返回</el-button
>
<el-button size="small" @click="back" type="primary" plain :icon="BackIcon"
>返回
</el-button>
<el-form
class="query-form"
inline
@ -27,20 +27,47 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleSearch"
<el-button type="primary" :icon="Search" @click="handleSearch"
>搜索
</el-button>
<el-button type="primary" icon="refresh" @click="resetQuery"
<el-button type="primary" :icon="Refresh" @click="resetQuery"
>重置
</el-button>
</el-form-item>
</el-form>
<el-table :data="logList" v-loading>
<el-table-column label="访问时间" prop="visitTime"></el-table-column>
<el-table-column label="访问IP" prop="visitIp"></el-table-column>
<el-table-column label="访问地区" prop="visitAddress"></el-table-column>
<el-table-column label="访问设备" prop="visitDevice"></el-table-column>
<el-table-column label="访问后缀" prop="urlSuffix"></el-table-column>
<el-table-column
label="访问后缀"
prop="urlSuffix"
align="center"
></el-table-column>
<el-table-column
label="访问时间"
prop="visitTime"
align="center"
></el-table-column>
<el-table-column
label="访问IP"
prop="visitIp"
align="center"
></el-table-column>
<el-table-column
label="访问地区"
prop="visitAddress"
align="center"
></el-table-column>
<el-table-column
label="访问设备"
prop="visitDevice"
align="center"
></el-table-column>
<el-table-column label="访问结果" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.visitType === '0'" type="success">成功</el-tag>
<el-tag v-else-if="row.visitType === '1'" type="danger">失败</el-tag>
<el-tag v-else type="warning">未知</el-tag>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
@ -59,6 +86,7 @@
import { reactive, ref, toRefs } from "vue";
import { listLogList } from "@/api/shortlink";
import type { LogListParams } from "@/types/LogListParams";
import { Back as BackIcon, Refresh, Search } from "@element-plus/icons-vue";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();

View File

@ -2,13 +2,13 @@
<div id="app-container" class="p-3">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button @click="handleAdd" type="primary" size="small" icon="plus"
<el-button @click="handleAdd" type="primary" size="small" :icon="Plus"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="delete"
:icon="Delete"
@click="handleDelete()"
type="danger"
size="small"
@ -89,6 +89,13 @@
label="过期时间"
prop="validityTime"
></el-table-column>
<el-table-column label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === '0'" type="success">启用</el-tag>
<el-tag v-else-if="row.status === '1'" type="danger">禁用</el-tag>
<el-tag v-else type="warning">未知</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作">
<template #default="{ row }">
<el-button
@ -143,14 +150,33 @@
></el-input>
</el-form-item>
<el-form-item label="有效期" prop="validityTime">
<el-form-item ref="validityTimeRef" label="有效期" prop="validityTime">
<el-radio-group
:style="{ marginRight: '10px' }"
v-model="isPermanent"
@change="changePermanent"
>
<el-radio :label="true">永久</el-radio>
<el-radio :label="false">自定义</el-radio>
</el-radio-group>
<el-date-picker
v-if="!isPermanent"
value-format="YYYY-MM-DD HH:mm:ss"
v-model="form.validityTime"
type="datetime"
placeholder="请选择过期时间"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="form.status"
inactive-value="1"
active-value="0"
inactive-text="禁用"
active-text="启用"
></el-switch>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
@ -191,11 +217,13 @@ import {
import { reactive, ref, toRefs } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import type { ShortLink } from "@/types/ShortLink";
import { CopyDocument, Delete, Plus } from "@element-plus/icons-vue";
import QrcodeVue from "qrcode.vue";
import { useRouter } from "vue-router";
const qrcodeRef = ref();
const isPermanent = ref(false);
const shortLinkList = ref<ShortLink[]>([]);
const showEditDialog = ref(false);
const editDialogTitle = ref<string>("");
@ -245,8 +273,13 @@ const data = reactive<{
},
});
const { form, queryParams, rules } = toRefs(data);
const validityTimeRef = ref();
const changePermanent = (status: boolean) => {
if (status) validityTimeRef.value.clearValidate();
};
const total = ref<number>(0);
const router = useRouter();
const getList = async () => {
const resp = await listShortLink(queryParams.value);
shortLinkList.value = resp.data.rows;
@ -327,6 +360,10 @@ const handleDelete = (id?: string) => {
};
const submitForm = async () => {
if (isPermanent.value) {
form.value.validityTime = "2099-12-12 23:59:59";
}
await formRef.value.validate();
if (form.value.id) {
await updateShortLink(form.value);
ElMessage.success("短链接修改成功");

283
src/views/Login.vue Normal file
View File

@ -0,0 +1,283 @@
<template>
<div class="login">
<el-form
ref="loginRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">短链接生成</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
size="large"
auto-complete="off"
placeholder="账号"
>
<template #prefix>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
size="large"
auto-complete="off"
placeholder="密码"
@keyup.enter="handleLogin"
>
<template #prefix>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- <el-form-item prop="code" v-if="captchaEnabled">-->
<!-- <el-input-->
<!-- v-model="loginForm.code"-->
<!-- size="large"-->
<!-- auto-complete="off"-->
<!-- placeholder="验证码"-->
<!-- style="width: 63%"-->
<!-- @keyup.enter="handleLogin"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <svg-icon-->
<!-- icon-class="validCode"-->
<!-- class="el-input__icon input-icon"-->
<!-- />-->
<!-- </template>-->
<!-- </el-input>-->
<!-- <div class="login-code">-->
<!-- <img :src="codeUrl" @click="getCode" class="login-code-img" />-->
<!-- </div>-->
<!-- </el-form-item>-->
<el-checkbox
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码
</el-checkbox>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="large"
type="primary"
style="width: 100%"
@click.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
<!-- <div style="float: right" v-if="register">-->
<!-- <router-link class="link-type" :to="'/register'"-->
<!-- >立即注册-->
<!-- </router-link>-->
<!-- </div>-->
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2022 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Lock, User } from "@element-plus/icons-vue";
import { decrypt, encrypt } from "@/utils/jsencrypt";
import Cookies from "js-cookie";
import { useUserStore } from "@/store/user";
import { useRouter } from "vue-router";
const userStore = useUserStore();
const loginForm: any = ref({
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: "",
});
const router = useRouter();
const loginRules = {
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
code: [{ required: true, trigger: "change", message: "请输入验证码" }],
};
const loginRef = ref();
const captchaEnabled = ref(false);
const codeUrl = ref("");
const loading = ref(false);
const redirect = ref(undefined);
function handleLogin() {
loginRef.value.validate((valid: boolean) => {
if (valid) {
loading.value = true;
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 });
Cookies.set("password", encrypt(loginForm.value.password), {
expires: 30,
});
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
} else {
// 否则移除
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
}
// 调用action的登录方法
userStore
.login(loginForm.value)
.then(() => {
router.push({ path: redirect.value || "/" });
})
.catch(() => {
loading.value = false;
// 重新获取验证码
// if (captchaEnabled.value) {
// getCode();
// }
});
}
});
}
function getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password:
password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
};
}
getCookie();
</script>
<!--<script setup>-->
<!--import { getCodeImg } from "@/api/login";-->
<!--import Cookies from "js-cookie";-->
<!--import { encrypt, decrypt } from "@/utils/jsencrypt";-->
<!--import useUserStore from "@/store/modules/user";-->
<!--const userStore = useUserStore();-->
<!--const router = useRouter();-->
<!--const { proxy } = getCurrentInstance();-->
<!---->
<!--const loading = ref(false);-->
<!--// 验证码开关-->
<!---->
<!--// 注册开关-->
<!--const register = ref(false);-->
<!--const redirect = ref(undefined);-->
<!--function getCode() {-->
<!-- getCodeImg().then((res) => {-->
<!-- captchaEnabled.value =-->
<!-- res.captchaOnOff === undefined ? true : res.captchaOnOff;-->
<!-- register.value = res.registerUser === undefined ? true : res.registerUser;-->
<!-- if (captchaEnabled.value) {-->
<!-- codeUrl.value = "data:image/gif;base64," + res.img;-->
<!-- loginForm.value.uuid = res.uuid;-->
<!-- }-->
<!-- });-->
<!--}-->
<!--function getCookie() {-->
<!-- const username = Cookies.get("username");-->
<!-- const password = Cookies.get("password");-->
<!-- const rememberMe = Cookies.get("rememberMe");-->
<!-- loginForm.value = {-->
<!-- username: username === undefined ? loginForm.value.username : username,-->
<!-- password:-->
<!-- password === undefined ? loginForm.value.password : decrypt(password),-->
<!-- rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),-->
<!-- };-->
<!--}-->
<!--getCode();-->
<!--getCookie();-->
<!--</script>-->
<style lang="scss" scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-image: url("../assets/login-background.jpg");
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 40px;
input {
height: 40px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 40px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 40px;
padding-left: 12px;
}
</style>

View File

@ -1,11 +1,8 @@
/** @type {import("tailwindcss").Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {}
extend: {},
},
plugins: []
plugins: [],
};

View File

@ -1,6 +1,11 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"types": ["node"]

View File

@ -2,23 +2,48 @@ 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://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [
vue(),
AutoImport({
resolvers: [
ElementPlusResolver(),
// IconsResolver({
// prefix: "Icon"
// })
],
}),
Components({
resolvers: [
// Auto register icon components
// 自动注册图标组件
// IconsResolver({
// enabledCollections: ["ep"]
// }),
ElementPlusResolver(),
],
}),
// Icons({
// autoInstall: true
// })
],
server: {
proxy: {
"/dev-api": {
target: "http://118.195.192.58:1618",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, "")
}
}
rewrite: (p) => p.replace(/^\/dev-api/, ""),
},
},
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
}
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});