初始化项目

This commit is contained in:
Unique-Jerry 2023-10-18 16:59:06 +08:00
commit 27766bf8a5
22 changed files with 3535 additions and 0 deletions

40
.eslintrc.cjs Normal file
View File

@ -0,0 +1,40 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' // 换行符号不限制win mac 不一致)
}
],
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成忽略index.vue
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
// 💡 添加未定义变量错误提示create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
},
globals: {
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly'
}
}

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# 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?

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# Ai-interviewer-ui
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) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

13
index.html Normal file
View File

@ -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.js"></script>
</body>
</html>

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "ai-interviewer-ui",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"lint-staged": "lint-staged"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.5.1",
"element-plus": "^2.4.0",
"pinia": "^2.1.6",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@vitejs/plugin-vue": "^4.3.4",
"@vue/eslint-config-prettier": "^8.0.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"lint-staged": "^15.0.1",
"pinia-plugin-persistedstate": "^3.2.0",
"prettier": "^3.0.3",
"sass": "^1.69.4",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.9"
}
}

2949
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

11
src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>

8
src/api/user.js Normal file
View File

@ -0,0 +1,8 @@
import request from "@/utils/request.js"
export const adminLogin=({phone,encoding,password})=>request.post("admin/login",{
phone,
encoding,
password
})

BIN
src/assets/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/login_bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

13
src/main.js Normal file
View File

@ -0,0 +1,13 @@
import { createApp } from 'vue'
import pinia from "@/stores";
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')

16
src/router/index.js Normal file
View File

@ -0,0 +1,16 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from "@/stores";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/login', component: () => import('@/views/login/LoginPage.vue') },
{ path: '/', component: () => import('@/views/layout/LayoutContainer.vue') }
]
})
router.beforeEach((to) => {
const userStore = useUserStore()
if (!userStore.token && to.path !== '/login') return '/login'
})
export default router

10
src/stores/index.js Normal file
View File

@ -0,0 +1,10 @@
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia
export * from "@/stores/modules/userStore.js"

View File

@ -0,0 +1,30 @@
import { defineStore } from "pinia"
import { ref } from "vue";
export const useUserStore=defineStore('interviewer-user',()=>{
const token = ref('')
const setToken = (newToken) => {
token.value = newToken
}
const removeToken = () => {
token.value = ''
}
return {
token,
setToken,
removeToken
}
},{
persist: {
paths: ['token']
}
})

44
src/utils/request.js Normal file
View File

@ -0,0 +1,44 @@
import { useUserStore } from '@/stores'
import axios from 'axios'
import router from '@/router'
const baseURL = 'http://localhost'
const instance = axios.create({
baseURL,
timeout: 100000
})
instance.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = userStore.token
}
return config
},
(err) => Promise.reject(err)
)
instance.interceptors.response.use(
(res) => {
if (res.data.code === 0) {
return res
}
if (res.data.code === 401) {
ElMessage.error(res.data.message)
router.replace('/login')
}
ElMessage.error(res.data.message)
return Promise.reject(res)
},
(err) => {
if (err.response?.status === 401) {
router.replace('/login')
}
return Promise.reject(err)
}
)
export default instance
export { baseURL }

View File

@ -0,0 +1,128 @@
<script setup>
import {
Management,
Promotion,
UserFilled,
User,
Crop,
EditPen,
SwitchButton,
CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
</script>
<template>
<el-container class="layout-container">
<el-aside width="200px">
<div class="el-aside__logo"></div>
<el-menu
active-text-color="#ffd04b"
background-color="#232323"
:default-active="$route.path"
text-color="#fff"
router
>
<el-menu-item index="/article/channel">
<el-icon><Management /></el-icon>
<span>文章分类</span>
</el-menu-item>
<el-menu-item index="/article/manage">
<el-icon><Promotion /></el-icon>
<span>文章管理</span>
</el-menu-item>
<el-sub-menu index="/user">
<template #title>
<el-icon><UserFilled /></el-icon>
<span>个人中心</span>
</template>
<el-menu-item index="/user/profile">
<el-icon><User /></el-icon>
<span>基本资料</span>
</el-menu-item>
<el-menu-item index="/user/avatar">
<el-icon><Crop /></el-icon>
<span>更换头像</span>
</el-menu-item>
<el-menu-item index="/user/password">
<el-icon><EditPen /></el-icon>
<span>重置密码</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-container>
<el-header>
<div>黑马程序员<strong>小帅鹏</strong></div>
<el-dropdown placement="bottom-end">
<span class="el-dropdown__box">
<el-avatar :src="avatar" />
<el-icon><CaretBottom /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile" :icon="User"
>基本资料</el-dropdown-item
>
<el-dropdown-item command="avatar" :icon="Crop"
>更换头像</el-dropdown-item
>
<el-dropdown-item command="password" :icon="EditPen"
>重置密码</el-dropdown-item
>
<el-dropdown-item command="logout" :icon="SwitchButton"
>退出登录</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
</el-container>
</el-container>
</template>
<style lang="scss" scoped>
.layout-container {
height: 100vh;
.el-aside {
background-color: #232323;
&__logo {
height: 120px;
background: url('@/assets/logo.png') no-repeat center / 120px auto;
}
.el-menu {
border-right: none;
}
}
.el-header {
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
.el-dropdown__box {
display: flex;
align-items: center;
.el-icon {
color: #999;
margin-left: 10px;
}
&:active,
&:focus {
outline: none;
}
}
}
.el-footer {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
}
}
</style>

View File

@ -0,0 +1,134 @@
<script setup>
import { Lock, User } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { adminLogin } from '@/api/user.js'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
const form = ref()
const formModel = ref({
phone: '',
encoding: '',
password: ''
})
const rules = {
phone: [
{ required: true, message: '请输入账号!', trigger: 'blur' },
{
min: 10,
max: 12,
message: '请输入正确的账号!',
trigger: 'blur'
}
],
encoding: [{ required: true, message: '请输入公司编码!', trigger: 'blur' }],
password: [
{ required: true, message: '请输入密码!', trigger: 'blur' },
{
min: 5,
max: 10,
message: '字符长度为5-10个字符',
trigger: 'blur'
}
]
}
const userStore = useUserStore()
const router = useRouter()
const goLogin = async () => {
await form.value.validate()
const res = await adminLogin(formModel.value)
console.log(res)
userStore.setToken(res.data.data.token)
ElMessage.success('登录成功')
router.push('/')
}
</script>
<template>
<el-row class="login-page">
<el-col :span="12" class="bg"></el-col>
<el-col :span="6" :offset="3" class="form">
<el-form
:model="formModel"
:rules="rules"
ref="form"
size="large"
autocomplete="off"
>
<el-form-item>
<h1>登录</h1>
</el-form-item>
<el-form-item prop="phone">
<el-input
:prefix-icon="User"
placeholder="请输入账号"
v-model="formModel.phone"
></el-input>
</el-form-item>
<el-form-item prop="encoding">
<el-input
name="encoding"
:prefix-icon="Lock"
placeholder="请输入公司编码"
v-model="formModel.encoding"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
name="password"
:prefix-icon="Lock"
type="password"
placeholder="请输入密码"
v-model="formModel.password"
></el-input>
</el-form-item>
<el-form-item class="flex">
<div class="flex">
<el-checkbox>记住我</el-checkbox>
<el-link type="primary" :underline="false">忘记密码</el-link>
</div>
</el-form-item>
<el-form-item>
<el-button
class="button"
type="primary"
auto-insert-space
@click="goLogin"
>登录</el-button
>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<style scoped>
.login-page {
height: 100vh;
background-color: #fff;
.bg {
background: url('@/assets/login_bg.jpg') no-repeat center / cover;
border-radius: 0 20px 20px 0;
}
.form {
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;
.title {
margin: 0 auto;
}
.button {
width: 100%;
}
.flex {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
</style>

25
vite.config.js Normal file
View File

@ -0,0 +1,25 @@
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(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})