login
This commit is contained in:
@ -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",
|
||||
},
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
{}
|
||||
{}
|
||||
|
@ -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
5
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
34
components.d.ts
vendored
Normal file
34
components.d.ts
vendored
Normal 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']
|
||||
}
|
||||
}
|
20
index.html
20
index.html
@ -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>
|
||||
|
@ -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
948
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
10
src/App.vue
10
src/App.vue
@ -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
42
src/api/login.ts
Normal 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",
|
||||
});
|
||||
}
|
BIN
src/assets/login-background.jpg
Normal file
BIN
src/assets/login-background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 509 KiB |
22
src/main.ts
22
src/main.ts
@ -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
40
src/permission.ts
Normal 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}`); // 否则全部重定向到登录页
|
||||
}
|
||||
}
|
||||
});
|
@ -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
5
src/store/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
const store = createPinia();
|
||||
|
||||
export default store;
|
73
src/store/user.ts
Normal file
73
src/store/user.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
@ -5,4 +5,5 @@ export interface ShortLink {
|
||||
urlPrefix?: string;
|
||||
urlSuffix?: string;
|
||||
validityTime?: string;
|
||||
status?: boolean;
|
||||
}
|
||||
|
16
src/utils/auth.ts
Normal file
16
src/utils/auth.ts
Normal 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
31
src/utils/jsencrypt.ts
Normal 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); // 对数据进行解密
|
||||
}
|
@ -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) => {
|
||||
// 未设置状态码则默认成功状态
|
||||
|
@ -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();
|
||||
|
@ -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
283
src/views/Login.vue
Normal 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>
|
@ -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: [],
|
||||
};
|
||||
|
@ -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"]
|
||||
|
@ -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)),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user