commit
752a75a398
26 changed files with 2359 additions and 0 deletions
-
30.gitignore
-
3.vscode/extensions.json
-
33README.md
-
1env.d.ts
-
13index.html
-
1828package-lock.json
-
33package.json
-
BINpublic/favicon.ico
-
14src/App.vue
-
BINsrc/assets/images/login.jpg
-
BINsrc/assets/images/nav1.png
-
BINsrc/assets/images/nav10.png
-
BINsrc/assets/images/nav13.png
-
155src/components/admin/SignIn.vue
-
11src/components/blogs/HomePage.vue
-
50src/hooks/intex.ts
-
12src/main.ts
-
0src/router/admin.ts
-
0src/router/blog.ts
-
19src/router/index.ts
-
12src/stores/counter.ts
-
85src/tools/request.ts
-
14tsconfig.app.json
-
11tsconfig.json
-
19tsconfig.node.json
-
16vite.config.ts
@ -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 |
@ -0,0 +1,3 @@ |
|||
{ |
|||
"recommendations": ["Vue.volar"] |
|||
} |
@ -0,0 +1,33 @@ |
|||
# blog_front |
|||
|
|||
This template should help get you started developing with Vue 3 in Vite. |
|||
|
|||
## Recommended IDE Setup |
|||
|
|||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). |
|||
|
|||
## Type Support for `.vue` Imports in TS |
|||
|
|||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. |
|||
|
|||
## Customize configuration |
|||
|
|||
See [Vite Configuration Reference](https://vitejs.dev/config/). |
|||
|
|||
## Project Setup |
|||
|
|||
```sh |
|||
npm install |
|||
``` |
|||
|
|||
### Compile and Hot-Reload for Development |
|||
|
|||
```sh |
|||
npm run dev |
|||
``` |
|||
|
|||
### Type-Check, Compile and Minify for Production |
|||
|
|||
```sh |
|||
npm run build |
|||
``` |
@ -0,0 +1 @@ |
|||
/// <reference types="vite/client" />
|
@ -0,0 +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>Vite App</title> |
|||
</head> |
|||
<body> |
|||
<div id="app"></div> |
|||
<script type="module" src="/src/main.ts"></script> |
|||
</body> |
|||
</html> |
1828
package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,33 @@ |
|||
{ |
|||
"name": "blog_front", |
|||
"version": "0.0.0", |
|||
"private": true, |
|||
"type": "module", |
|||
"scripts": { |
|||
"dev": "vite", |
|||
"build": "run-p type-check \"build-only {@}\" --", |
|||
"preview": "vite preview", |
|||
"build-only": "vite build", |
|||
"type-check": "vue-tsc --build --force" |
|||
}, |
|||
"dependencies": { |
|||
"ant-design-vue": "^4.2.1", |
|||
"axios": "^1.7.2", |
|||
"echarts": "^5.5.0", |
|||
"normalize.css": "^8.0.1", |
|||
"pinia": "^2.1.7", |
|||
"simplebar-vue": "^2.3.4", |
|||
"vue": "^3.4.21", |
|||
"vue-router": "^4.3.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@tsconfig/node20": "^20.1.4", |
|||
"@types/node": "^20.12.5", |
|||
"@vitejs/plugin-vue": "^5.0.4", |
|||
"@vue/tsconfig": "^0.5.1", |
|||
"npm-run-all2": "^6.1.2", |
|||
"typescript": "~5.4.0", |
|||
"vite": "^5.2.8", |
|||
"vue-tsc": "^2.0.11" |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
<template> |
|||
<Simplebar style="height: 100vh;overflow: auto;"> |
|||
<RouterView/> |
|||
</Simplebar> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import Simplebar from 'simplebar-vue'; |
|||
import 'simplebar-vue/dist/simplebar.min.css'; |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
After Width: 1920 | Height: 1080 | Size: 48 KiB |
After Width: 2560 | Height: 1440 | Size: 449 KiB |
After Width: 2560 | Height: 1440 | Size: 1.3 MiB |
After Width: 2560 | Height: 1440 | Size: 735 KiB |
@ -0,0 +1,155 @@ |
|||
<template> |
|||
<context-holder /> |
|||
<div id="container"> |
|||
<a-spin v-if="loading" tip="Loading..."></a-spin> |
|||
<a-form v-else :model="formState" name="normal_login" class="login-form" @submit.prevent="login"> |
|||
<h2>博客管理系统</h2> |
|||
<a-form-item label="用户名" name="username" :rules="[{ required: true, validator: verifyName }]"> |
|||
<a-input v-model:value="formState.username" placeholder="请输入用户名"> |
|||
<template #prefix> |
|||
<UserOutlined class="site-form-item-icon" /> |
|||
</template> |
|||
</a-input> |
|||
</a-form-item> |
|||
|
|||
<a-form-item label="密 码" name="password" |
|||
:rules="[{ required: true, validator: verifyPassword }]"> |
|||
<a-input-password v-model:value="formState.password" placeholder="请输入密码"> |
|||
<template #prefix> |
|||
<LockOutlined class="site-form-item-icon" /> |
|||
</template> |
|||
</a-input-password> |
|||
</a-form-item> |
|||
<a-form-item> |
|||
<a-form-item name="remember" no-style> |
|||
<a-checkbox v-model:checked="formState.remember">记住我</a-checkbox> |
|||
</a-form-item> |
|||
<a class="login-form-forgot" href="">忘记密码?</a> |
|||
</a-form-item> |
|||
|
|||
<a-form-item> |
|||
<a-button type="primary" html-type="submit" class="login-form-button" :disabled="disabled"> |
|||
登 录 |
|||
</a-button> |
|||
Or |
|||
<a href="">注册</a> |
|||
</a-form-item> |
|||
</a-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, computed, ref, onMounted } from 'vue'; |
|||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { post } from '@/tools/request' |
|||
import { message } from 'ant-design-vue'; |
|||
import { validator } from '@/hooks/intex' |
|||
const [messageApi, contextHolder] = message.useMessage(); |
|||
|
|||
interface FormState { |
|||
username: string; |
|||
password: string; |
|||
remember: boolean; |
|||
} |
|||
const verifyName = validator(4, 10, { |
|||
info: '请输入用户名', |
|||
tooshort: '用户名不能少于4个字符', |
|||
toolong: '用户名不能超过10个字符', |
|||
}); |
|||
const verifyPassword = validator(6, 10, { |
|||
info: "请输入密码", |
|||
tooshort: "密码不能少于6个字符", |
|||
toolong: "密码不能超过10个字符" |
|||
}); |
|||
const formState = reactive<FormState>({ |
|||
username: '', |
|||
password: '', |
|||
remember: false, |
|||
}); |
|||
const loading = ref(false) |
|||
const disabled = computed(() => { |
|||
return !(formState.username && formState.password); |
|||
}); |
|||
const router = useRouter(); |
|||
const login = async () => { |
|||
try { |
|||
loading.value = true; |
|||
const response = await post( |
|||
'users/token', |
|||
{ |
|||
username: formState.username, |
|||
password: formState.password, |
|||
}, |
|||
false, |
|||
{ |
|||
'Accept': 'application/json', |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
} |
|||
); |
|||
|
|||
const token = response.data.access_token; |
|||
localStorage.setItem('token', token); |
|||
messageApi.success("登录成功") |
|||
if (formState.remember) { |
|||
localStorage.setItem('rememberedUsername', formState.username); |
|||
} else { |
|||
localStorage.removeItem('rememberedUsername'); |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
router.push('/admin/dashboard'); |
|||
}, 2000); |
|||
} catch (error) { |
|||
messageApi.error('用户或密码输入错误'); |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
onMounted(() => { |
|||
const rememberedUsername = localStorage.getItem('rememberedUsername'); |
|||
if (rememberedUsername) { |
|||
formState.username = rememberedUsername; |
|||
formState.remember = true; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
#container { |
|||
display: flex; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
#container::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-image: url('@/assets/images/login.jpg'); |
|||
opacity: 0.2; |
|||
/* 设置透明度 */ |
|||
} |
|||
|
|||
h2 { |
|||
text-align: center; |
|||
color: rgba(41, 39, 51, 0.88); |
|||
} |
|||
|
|||
#components-form-demo-normal-login .login-form { |
|||
max-width: 300px; |
|||
} |
|||
|
|||
#components-form-demo-normal-login .login-form-forgot { |
|||
float: right; |
|||
} |
|||
|
|||
#components-form-demo-normal-login .login-form-button { |
|||
width: 100%; |
|||
} |
|||
</style> |
@ -0,0 +1,11 @@ |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
|
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,50 @@ |
|||
import * as echarts from "echarts"; |
|||
|
|||
interface inputMessage { |
|||
info: string; |
|||
tooshort: string; |
|||
toolong: string; |
|||
} |
|||
|
|||
interface selectMessage { |
|||
info: string; |
|||
} |
|||
|
|||
/** |
|||
* 定义antd vue的验证规则 |
|||
*/ |
|||
export const validator = ( |
|||
minLength: number, |
|||
maxLength: number, |
|||
message: inputMessage |
|||
) => { |
|||
return (_: any, value: string) => { |
|||
if (!value) { |
|||
return Promise.reject(message.info); |
|||
} else if (value.length < minLength) { |
|||
return Promise.reject(message.tooshort); |
|||
} else if (value.length > maxLength) { |
|||
return Promise.reject(message.toolong); |
|||
} else { |
|||
return Promise.resolve(); |
|||
} |
|||
}; |
|||
}; |
|||
|
|||
export const verifySelect = (message: selectMessage) => { |
|||
return (_: any, value: string) => { |
|||
if (!value) { |
|||
return Promise.reject(message.info); |
|||
} else { |
|||
return Promise.resolve(); |
|||
} |
|||
}; |
|||
}; |
|||
|
|||
/** |
|||
* echarts配置 |
|||
*/ |
|||
export const createEcharts = (chartRef: any, option: any) => { |
|||
const myChart = echarts.init(chartRef.value); |
|||
myChart.setOption(option); |
|||
}; |
@ -0,0 +1,12 @@ |
|||
import { createApp } from 'vue' |
|||
import { createPinia } from 'pinia' |
|||
import "normalize.css" |
|||
import App from './App.vue' |
|||
import router from './router' |
|||
import Antd from 'ant-design-vue'; |
|||
const app = createApp(App) |
|||
|
|||
app.use(createPinia()) |
|||
app.use(router) |
|||
app.use(Antd) |
|||
app.mount('#app') |
@ -0,0 +1,19 @@ |
|||
import { createRouter, createWebHistory } from 'vue-router' |
|||
|
|||
const router = createRouter({ |
|||
history: createWebHistory(import.meta.env.BASE_URL), |
|||
routes: [ |
|||
{ |
|||
path: "/", |
|||
name: "page", |
|||
component: () => import("@/components/blogs/HomePage.vue"), |
|||
}, |
|||
{ |
|||
path: "/login", |
|||
name: "login", |
|||
component: () => import("@/components/admin/SignIn.vue"), |
|||
}, |
|||
] |
|||
}) |
|||
|
|||
export default router |
@ -0,0 +1,12 @@ |
|||
import { ref, computed } from 'vue' |
|||
import { defineStore } from 'pinia' |
|||
|
|||
export const useCounterStore = defineStore('counter', () => { |
|||
const count = ref(0) |
|||
const doubleCount = computed(() => count.value * 2) |
|||
function increment() { |
|||
count.value++ |
|||
} |
|||
|
|||
return { count, doubleCount, increment } |
|||
}) |
@ -0,0 +1,85 @@ |
|||
import axios from 'axios'; |
|||
import { type AxiosRequestConfig } from 'axios'; |
|||
import router from '@/router'; |
|||
const instance = axios.create({ |
|||
// 添加url
|
|||
baseURL: 'http://www.wuruilin.cn:8000/', |
|||
timeout: 5000, |
|||
}); |
|||
|
|||
// 请求拦截器
|
|||
instance.interceptors.request.use( |
|||
config => { |
|||
const token = localStorage.getItem('token'); |
|||
if (token) { |
|||
config.headers.Authorization = `Bearer ${token}`; |
|||
} |
|||
return config; |
|||
}, |
|||
error => { |
|||
return Promise.reject(error); |
|||
} |
|||
); |
|||
// 响应拦截器
|
|||
instance.interceptors.response.use( |
|||
response => { |
|||
return response; |
|||
}, |
|||
error => { |
|||
if (error.response && error.response.status === 401) { |
|||
router.push('/login'); |
|||
} |
|||
return Promise.reject(error); |
|||
} |
|||
); |
|||
|
|||
const request = (method: string, url: string, dataOrParams = {}, requiresAuth = false, headers: AxiosRequestConfig['headers'] = {}) => { |
|||
if (requiresAuth) { |
|||
const token = localStorage.getItem('token'); |
|||
if (!token) { |
|||
return Promise.reject(new Error('请先登录')); |
|||
} |
|||
headers.Authorization = `Bearer ${token}`; |
|||
} |
|||
// return instance({
|
|||
// method,
|
|||
// url,
|
|||
// data,
|
|||
// headers,
|
|||
// });
|
|||
let requestOptions: AxiosRequestConfig = { |
|||
method, |
|||
url, |
|||
headers, |
|||
}; |
|||
if (method.toLowerCase() === 'get' || method.toLowerCase() === 'delete') { |
|||
requestOptions.params = dataOrParams; |
|||
} else { |
|||
requestOptions.data = dataOrParams; |
|||
} |
|||
|
|||
return instance(requestOptions); |
|||
}; |
|||
|
|||
// 封装 GET 请求
|
|||
const get = (url: string, params = {}, requiresAuth = false, headers: AxiosRequestConfig['headers'] = {}) => { |
|||
return request('get', url, params, requiresAuth, headers); |
|||
}; |
|||
|
|||
|
|||
// 封装 POST 请求
|
|||
const post = (url: string, data = {}, requiresAuth = false, headers: AxiosRequestConfig['headers'] = {}) => { |
|||
return request('post', url, data, requiresAuth, headers); |
|||
}; |
|||
|
|||
// 封装 DELETE 请求
|
|||
const remove = (url: string, data = {}, requiresAuth = false, headers: AxiosRequestConfig['headers'] = {}) => { |
|||
return request('delete', url, data, requiresAuth, headers); |
|||
}; |
|||
|
|||
// 封装 PUT 请求
|
|||
const put = (url: string, data = {}, requiresAuth = false, headers: AxiosRequestConfig['headers'] = {}) => { |
|||
return request('put', url, data, requiresAuth, headers); |
|||
}; |
|||
|
|||
export { get, post, remove, put }; |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"extends": "@vue/tsconfig/tsconfig.dom.json", |
|||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue","src/**/*.ts"], |
|||
"exclude": ["src/**/__tests__/*"], |
|||
"compilerOptions": { |
|||
"composite": true, |
|||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
|||
|
|||
"baseUrl": ".", |
|||
"paths": { |
|||
"@/*": ["./src/*"] |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"files": [], |
|||
"references": [ |
|||
{ |
|||
"path": "./tsconfig.node.json" |
|||
}, |
|||
{ |
|||
"path": "./tsconfig.app.json" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"extends": "@tsconfig/node20/tsconfig.json", |
|||
"include": [ |
|||
"vite.config.*", |
|||
"vitest.config.*", |
|||
"cypress.config.*", |
|||
"nightwatch.conf.*", |
|||
"playwright.config.*" |
|||
], |
|||
"compilerOptions": { |
|||
"composite": true, |
|||
"noEmit": true, |
|||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
|||
|
|||
"module": "ESNext", |
|||
"moduleResolution": "Bundler", |
|||
"types": ["node"] |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
import { fileURLToPath, URL } from 'node:url' |
|||
|
|||
import { defineConfig } 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)) |
|||
} |
|||
} |
|||
}) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue