dsdq
This commit is contained in:
parent
fe8f8be86a
commit
62d2ead615
28
.docker-compose/nginx/conf.d/my.conf
Normal file
28
.docker-compose/nginx/conf.d/my.conf
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
client_max_body_size 100m;
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name localhost;
|
||||||
|
proxy_read_timeout 3600;
|
||||||
|
proxy_send_timeout 3600;
|
||||||
|
#charset koi8-r;
|
||||||
|
#access_log logs/host.access.log main;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||||
|
proxy_pass http://129.204.101.51:9999; # 设置代理服务器的协议和地址
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/swagger/index.html {
|
||||||
|
proxy_pass http://129.204.101.51:9999/swagger/index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
.env.development
Normal file
5
.env.development
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
VITE_APP_BASE_API = 'https://tools.1024tool.vip/'
|
||||||
|
VITE_SERVE = "https://tools.1024tool.vip/"
|
||||||
|
# VITE_SERVE = "http://192.168.210.29:18100/"
|
||||||
5
.env.production
Normal file
5
.env.production
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'production'
|
||||||
|
VITE_APP_BASE_API = 'https://tools.1024tool.vip/'
|
||||||
|
VITE_SERVE = "https://tools.1024tool.vip/"
|
||||||
|
|
||||||
5
.env.test
Normal file
5
.env.test
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
NODE_ENV = 'development'
|
||||||
|
|
||||||
|
VITE_APP_BASE_API = 'http://129.204.101.51:9999/'
|
||||||
|
VITE_SERVE = "http://129.204.101.51:9999/"
|
||||||
|
|
||||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
components.d.ts
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.vite
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
*.zip
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
FROM registry.cn-shanghai.aliyuncs.com/server1024/node:16.17.1 AS build-stage
|
||||||
|
|
||||||
|
WORKDIR /mht/
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 设置国内源并调试网络问题
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com
|
||||||
|
RUN npm config set network-timeout 600000
|
||||||
|
|
||||||
|
RUN npm install --legacy-peer-deps
|
||||||
|
# 确保 vite 已经全局安装
|
||||||
|
RUN npm install -g vite
|
||||||
|
|
||||||
|
# 检查 vite 是否安装成功
|
||||||
|
RUN vite --version
|
||||||
|
|
||||||
|
# 运行构建命令
|
||||||
|
RUN npm run build:prod
|
||||||
|
|
||||||
|
FROM registry.cn-shanghai.aliyuncs.com/server1024/nginx:base AS production-stage
|
||||||
|
|
||||||
|
COPY .docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf
|
||||||
|
COPY --from=build-stage /mht/dist /usr/share/nginx/html
|
||||||
|
RUN ls -al /usr/share/nginx/html
|
||||||
11
auto-imports.d.ts
vendored
Normal file
11
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||||
|
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||||
|
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||||
|
}
|
||||||
19
index.html
Normal file
19
index.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh_CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>儿科后台管理</title>
|
||||||
|
<meta name="keywords" content="儿科后台管理">
|
||||||
|
<meta name="description" content="儿科后台管理">
|
||||||
|
<!-- <script src="//at.alicdn.com/t/c/font_4896732_5thx9wsstok.js"></script> -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
69
package.json
Normal file
69
package.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"name": "mihoutao",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --open",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:test": "vite build --mode test",
|
||||||
|
"build:prod": "vite build --mode production",
|
||||||
|
"build:no": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"fix": "eslint src --fix",
|
||||||
|
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
|
||||||
|
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
|
||||||
|
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "2.1.0",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "next",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"default-passive-events": "^2.0.0",
|
||||||
|
"element-plus": "^2.8.0",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.1.6",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.0",
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-router": "^4.2.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/eslint-parser": "^7.22.9",
|
||||||
|
"@iconify-json/ep": "^1.1.11",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"eslint": "^8.45.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.15.1",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"postcss": "^8.4.27",
|
||||||
|
"postcss-html": "^1.5.0",
|
||||||
|
"postcss-pxtorem": "^6.1.0",
|
||||||
|
"postcss-scss": "^4.0.6",
|
||||||
|
"sass": "^1.64.1",
|
||||||
|
"sass-loader": "^13.3.2",
|
||||||
|
"stylelint": "^15.10.2",
|
||||||
|
"stylelint-config-prettier": "^9.0.5",
|
||||||
|
"stylelint-config-recess-order": "^4.3.0",
|
||||||
|
"stylelint-config-recommended-scss": "^12.0.0",
|
||||||
|
"stylelint-config-standard": "^34.0.0",
|
||||||
|
"stylelint-config-standard-scss": "^10.0.0",
|
||||||
|
"stylelint-config-standard-vue": "^1.0.0",
|
||||||
|
"stylelint-order": "^6.0.3",
|
||||||
|
"stylelint-scss": "^5.0.1",
|
||||||
|
"typescript": "^5.0.2",
|
||||||
|
"unplugin-auto-import": "^0.16.6",
|
||||||
|
"unplugin-icons": "^0.16.5",
|
||||||
|
"unplugin-vue-components": "^0.25.1",
|
||||||
|
"vite": "^4.4.5",
|
||||||
|
"vite-plugin-mock": "2.9.6",
|
||||||
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
|
"vue-tsc": "^1.8.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
postcss.config.cjs
Normal file
8
postcss.config.cjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
// plugins: {
|
||||||
|
// 'postcss-pxtorem': {
|
||||||
|
// rootValue: 100, // 设置根元素字体大小,1rem = 16px
|
||||||
|
// propList: ['*'], // 将所有属性都转换成rem
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
};
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
20
src/App.vue
Normal file
20
src/App.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<component :is="Component" />
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
#app {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
91
src/api/login.ts
Normal file
91
src/api/login.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export const userLogin = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/login`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 注册
|
||||||
|
export const userRegister = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/register`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
export const sendEmail = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/send_activation_email`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
export const getUserInfo = () => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/info`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活账号
|
||||||
|
|
||||||
|
export const verifyCode = (code: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/verify_activation_email/${code}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码登录
|
||||||
|
|
||||||
|
export const getRr = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/qr`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 轮询登录结果
|
||||||
|
|
||||||
|
export const getRrLogin = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/qr_login`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getUserSearch = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/search`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUserRecharge = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/recharge`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 充值记录
|
||||||
|
export const rechargeList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/recharge/list`,
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
91
src/api/news.ts
Normal file
91
src/api/news.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export const userLogin = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/login`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 注册
|
||||||
|
export const userRegister = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/register`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮箱验证码
|
||||||
|
export const sendEmail = (data: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/send_activation_email`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
export const getUserInfo = () => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/info`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 激活账号
|
||||||
|
|
||||||
|
export const verifyCode = (code: any) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/verify_activation_email/${code}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码登录
|
||||||
|
|
||||||
|
export const getRr = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/qr`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 轮询登录结果
|
||||||
|
|
||||||
|
export const getRrLogin = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/qr_login`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getUserSearch = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/search`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUserRecharge = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/recharge`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 充值记录
|
||||||
|
export const rechargeList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: `api/customer/recharge/list`,
|
||||||
|
method: 'get',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
BIN
src/assets/images/error_images/401.png
Normal file
BIN
src/assets/images/error_images/401.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
src/assets/images/error_images/404.png
Normal file
BIN
src/assets/images/error_images/404.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
src/assets/images/error_images/cloud.png
Normal file
BIN
src/assets/images/error_images/cloud.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
1
src/assets/images/logo.svg
Normal file
1
src/assets/images/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.1 KiB |
BIN
src/assets/images/welcome.png
Normal file
BIN
src/assets/images/welcome.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
35
src/components/SvgIcon/index.vue
Normal file
35
src/components/SvgIcon/index.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<svg :style="{ width: width, height: height }">
|
||||||
|
<use :xlink:href="prefix + name" :fill="color"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps({
|
||||||
|
//xlink:href属性值的前缀
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: '#icon-',
|
||||||
|
},
|
||||||
|
//svg矢量图的名字
|
||||||
|
name: String,
|
||||||
|
//svg图标的颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
//svg宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '16px',
|
||||||
|
},
|
||||||
|
//svg高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '16px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
||||||
20
src/components/index.ts
Normal file
20
src/components/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import SvgIcon from './SvgIcon/index.vue'
|
||||||
|
|
||||||
|
import type { App, Component } from 'vue'
|
||||||
|
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const allGlobalComponent: Component = { SvgIcon }
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(app: App) {
|
||||||
|
Object.keys(allGlobalComponent).forEach((key: string) => {
|
||||||
|
// 注册为全局组件
|
||||||
|
app.component(key, allGlobalComponent[key])
|
||||||
|
})
|
||||||
|
// 将 element-plus 的图标注册为全局组件
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
15
src/directive/has.ts
Normal file
15
src/directive/has.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 按钮权限的实现
|
||||||
|
import pinia from '@/store'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
|
||||||
|
export const isHasButton = (app: any) => {
|
||||||
|
// 自定义指令
|
||||||
|
app.directive('has', {
|
||||||
|
mounted(el: any, options: any) {
|
||||||
|
const userStore = useUserStore(pinia)
|
||||||
|
if (!userStore.buttons.includes(options.value)) {
|
||||||
|
el.parentNode.removeChild(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
2
src/eventBus/index.ts
Normal file
2
src/eventBus/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import mitt from 'mitt'
|
||||||
|
export default mitt()
|
||||||
129
src/layout/header/index.vue
Normal file
129
src/layout/header/index.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<el-header>
|
||||||
|
<div class="logo" @click="goHome">
|
||||||
|
<img src="@/assets/images/logo.svg" />
|
||||||
|
猕猴桃
|
||||||
|
</div>
|
||||||
|
<div class="tab">
|
||||||
|
<ul>
|
||||||
|
<li :class="{ active: actIndex == 0 }" @click="handelClick(0)">公众号</li>
|
||||||
|
<!-- <li :class="{ active: actIndex == 1 }" @click="handelClick(1)">视频号</li>
|
||||||
|
<li @click="toWebsite">AI私域</li>
|
||||||
|
<li :class="{ active: actIndex == 1 }" @click="handelClick(2)">API</li> -->
|
||||||
|
<li :class="{ active: actIndex == 3 }" @click="handelClick(3)">AI爆文生成</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Setting></Setting>
|
||||||
|
</el-header>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { GET_TOKEN } from '@/utils/token';
|
||||||
|
import Setting from '@/layout/setting/index.vue'
|
||||||
|
import emitter from '@/eventBus'
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const path = computed(() => {
|
||||||
|
return route.path
|
||||||
|
})
|
||||||
|
watch(path, (val) => {
|
||||||
|
if(val == '/dash'){
|
||||||
|
actIndex.value = 0
|
||||||
|
} else if(val == '/aiArticle'){
|
||||||
|
actIndex.value = 3
|
||||||
|
} else {
|
||||||
|
actIndex.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const goHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
const toWebsite = () => {
|
||||||
|
open('https://7vbot.1024tool.vip/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const actIndex = ref(null)
|
||||||
|
const handelClick = (index) => {
|
||||||
|
console.log(index)
|
||||||
|
if(index == 0){
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
actIndex.value = 0
|
||||||
|
router.push('/dash')
|
||||||
|
} else {
|
||||||
|
emitter.emit('userLogin', 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(index == 1) {
|
||||||
|
ElNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: ' 视频号开发中,敬请期待!',
|
||||||
|
})
|
||||||
|
} else if(index == 2){
|
||||||
|
open('https://apifox.com/apidoc/shared-a0bce534-8fcf-4b6e-a297-5b670c19a2b4/241940441e0')
|
||||||
|
} else if(index == 3){
|
||||||
|
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
actIndex.value = 3
|
||||||
|
router.push('/aiArticle')
|
||||||
|
} else {
|
||||||
|
emitter.emit('userLogin', 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-header {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
line-height: 70px;
|
||||||
|
padding: 0 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--el-box-shadow-light);
|
||||||
|
// background-color: #fff;
|
||||||
|
z-index: 9;
|
||||||
|
background-image: radial-gradient(transparent 1px,var(--bg-color) 1px);
|
||||||
|
background-size: 4px 4px;
|
||||||
|
backdrop-filter: saturate(50%) blur(8px);
|
||||||
|
.logo {
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 36px;
|
||||||
|
vertical-align: middle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.tab{
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
// margin-left: -100px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
li{
|
||||||
|
float: left;
|
||||||
|
padding: 0 20px;
|
||||||
|
// width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 70px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
color: var(--el-color-primary-light-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.active{
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
120
src/layout/home/header.vue
Normal file
120
src/layout/home/header.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="logo" @click="goHome">
|
||||||
|
<!-- <img src="@/assets/images/logo.svg" /> -->
|
||||||
|
<!-- 患者管理系统 -->
|
||||||
|
</div>
|
||||||
|
<div class="tab">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Setting></Setting>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { GET_TOKEN } from '@/utils/token';
|
||||||
|
import Setting from '@/layout/setting/index.vue'
|
||||||
|
import emitter from '@/eventBus'
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const path = computed(() => {
|
||||||
|
return route.path
|
||||||
|
})
|
||||||
|
watch(path, (val) => {
|
||||||
|
// if(val == '/dash'){
|
||||||
|
// actIndex.value = 0
|
||||||
|
// } else if(val == '/aiArticle'){
|
||||||
|
// actIndex.value = 3
|
||||||
|
// } else {
|
||||||
|
// actIndex.value = null
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
const goHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
const toWebsite = () => {
|
||||||
|
open('https://7vbot.1024tool.vip/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const actIndex = ref(null)
|
||||||
|
const handelClick = (index) => {
|
||||||
|
console.log(index)
|
||||||
|
if(index == 0){
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
actIndex.value = 0
|
||||||
|
router.push('/dash')
|
||||||
|
} else {
|
||||||
|
emitter.emit('userLogin', 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(index == 1) {
|
||||||
|
ElNotification({
|
||||||
|
type: 'warning',
|
||||||
|
message: ' 视频号开发中,敬请期待!',
|
||||||
|
})
|
||||||
|
} else if(index == 2){
|
||||||
|
open('https://apifox.com/apidoc/shared-a0bce534-8fcf-4b6e-a297-5b670c19a2b4/241940441e0')
|
||||||
|
} else if(index == 3){
|
||||||
|
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
actIndex.value = 3
|
||||||
|
router.push('/aiArticle')
|
||||||
|
} else {
|
||||||
|
emitter.emit('userLogin', 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
header {
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
line-height: 60px;
|
||||||
|
padding: 0px 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
// background-color: #fff;
|
||||||
|
z-index: 9;
|
||||||
|
// background-image: radial-gradient(transparent 1px,var(--bg-color) 1px);
|
||||||
|
// background-size: 4px 4px;
|
||||||
|
// backdrop-filter: saturate(50%) blur(8px);
|
||||||
|
.logo {
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 36px;
|
||||||
|
vertical-align: middle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.tab{
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
// margin-left: -100px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
li{
|
||||||
|
float: left;
|
||||||
|
padding: 0 20px;
|
||||||
|
// width: 100px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 60px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
color: var(--el-color-primary-light-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.active{
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
80
src/layout/home/index.vue
Normal file
80
src/layout/home/index.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="200px">
|
||||||
|
<AdminMenu />
|
||||||
|
</el-aside>
|
||||||
|
<el-container class="admin-container">
|
||||||
|
|
||||||
|
<el-header>
|
||||||
|
<Tabbar />
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<Transition name="fade" mode="out-in" enter-from-class="fade-enter">
|
||||||
|
<keep-alive :include="cachedComponents">
|
||||||
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
|
</Transition>
|
||||||
|
</router-view>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, computed } from 'vue'
|
||||||
|
|
||||||
|
import AdminMenu from './menu.vue'
|
||||||
|
import Tabbar from './header.vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter();
|
||||||
|
const cachedComponents = computed(() =>
|
||||||
|
router.getRoutes().filter((r) => r.meta.KeepAlive).map((r) => r.name)
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.common-layout {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.el-container{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.el-header {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-aside {
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: initial;
|
||||||
|
border-right: 1px solid var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-main {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
min-width: 1250px;
|
||||||
|
padding: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(50px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
src/layout/home/menu.vue
Normal file
55
src/layout/home/menu.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="menu-title">
|
||||||
|
患者管理系统
|
||||||
|
</div>
|
||||||
|
<el-menu :default-active="route.path" router>
|
||||||
|
<el-menu-item index="/user">
|
||||||
|
<el-icon><User /></el-icon>
|
||||||
|
患者管理
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/news">
|
||||||
|
<el-icon><Document /></el-icon>
|
||||||
|
健康文章
|
||||||
|
</el-menu-item>
|
||||||
|
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
const route = useRoute()
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-menu {
|
||||||
|
border-right: none;
|
||||||
|
background-color: #2b3a4a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
color: #fff;
|
||||||
|
&:hover {
|
||||||
|
background-color: #34495e;
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #2b3a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
height: 60px;
|
||||||
|
line-height: 60px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #2b3a4a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
133
src/layout/setting/index.vue
Normal file
133
src/layout/setting/index.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div class="setting">
|
||||||
|
<el-link type="danger" :underline="false" @click="logout">退出</el-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const router = useRouter()
|
||||||
|
const logout = () => {
|
||||||
|
localStorage.removeItem('TOKEN')
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.setting {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-div {
|
||||||
|
height: 90%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 400px;
|
||||||
|
padding: 60px 40px;
|
||||||
|
|
||||||
|
/* border-radius: 8px; */
|
||||||
|
/* box-shadow: var(--zuo-shadow); */
|
||||||
|
/* box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3); */
|
||||||
|
h2 {
|
||||||
|
font-size: 30px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-code {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-btn {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border: 0;
|
||||||
|
text-indent: 12px;
|
||||||
|
border-bottom: 1px solid var(--el-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
box-shadow: var(--el-box-shadow);
|
||||||
|
padding: 50px;
|
||||||
|
border-radius: var(--my-border-radius-large);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
// font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-box {
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 240px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-expire {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0.7);
|
||||||
|
color: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-loading-spinner) {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
40
src/main.ts
Normal file
40
src/main.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
// import 'virtual:svg-icons-register'
|
||||||
|
import globalComponent from './components/index'
|
||||||
|
import { router } from './router'
|
||||||
|
import pinia from './store'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
|
// default-passive-events 是一个轻量级的 JavaScript 库,旨在通过自动为支持 EventListenerOptions 的浏览器设置事件监听器的 { passive: true } 选项来优化滚动性能。这有助于减少触摸和滚轮事件处理时的延迟,从而提升用户体验。
|
||||||
|
import 'default-passive-events'
|
||||||
|
|
||||||
|
// 修改主题必须文件
|
||||||
|
import 'element-plus/theme-chalk/src/index.scss'
|
||||||
|
|
||||||
|
// 重置样式
|
||||||
|
import '@/styles/index.scss'
|
||||||
|
|
||||||
|
// 暗黑模式
|
||||||
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
|
|
||||||
|
// 自定义指令
|
||||||
|
// import { isHasButton } from '@/directive/has.ts'
|
||||||
|
|
||||||
|
// 屏幕自适应
|
||||||
|
// import './utils/fitter.js'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(ElementPlus, {
|
||||||
|
locale: zhCn,
|
||||||
|
})
|
||||||
|
app.use(globalComponent)
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
106
src/router/index.ts
Normal file
106
src/router/index.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
||||||
|
// import Nprogress from 'nprogress'
|
||||||
|
// import 'nprogress/nprogress.css'
|
||||||
|
import { GET_TOKEN } from '@/utils/token'
|
||||||
|
|
||||||
|
|
||||||
|
const constantRoutes = [
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Layout',
|
||||||
|
component: () => import('@/layout/home/index.vue'),
|
||||||
|
redirect: '/user',
|
||||||
|
meta: {
|
||||||
|
title: '首页'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
name: 'user',
|
||||||
|
component: () => import('@/views/user/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '患者管理'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/news',
|
||||||
|
name: 'news',
|
||||||
|
component: () => import('@/views/news/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '文章管理'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/addNew',
|
||||||
|
name: 'addNew',
|
||||||
|
component: () => import('@/views/news/addNew.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '添加文章'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('@/views/login/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '登录',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)',
|
||||||
|
name: 'Any',
|
||||||
|
component: () => import('@/views/404/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '404',
|
||||||
|
hidden: true,
|
||||||
|
icon: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: constantRoutes,
|
||||||
|
// 切换路由跳转到顶部
|
||||||
|
scrollBehavior() {
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const whiteList = ['/login']
|
||||||
|
|
||||||
|
// 全局前置守卫
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
// Nprogress.start()
|
||||||
|
const token = GET_TOKEN()
|
||||||
|
console.log(token)
|
||||||
|
if (token) {
|
||||||
|
if (to.path === '/login') {
|
||||||
|
next('/index')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (whiteList.indexOf(to.path) > -1) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 全局后置守卫
|
||||||
|
router.afterEach(() => {
|
||||||
|
// Nprogress.done()
|
||||||
|
})
|
||||||
|
|
||||||
8
src/store/index.ts
Normal file
8
src/store/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// 大仓库
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
|
export default pinia
|
||||||
13
src/store/modules/store.ts
Normal file
13
src/store/modules/store.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { defineStore } from "pinia"
|
||||||
|
|
||||||
|
const pinia = defineStore('main', {
|
||||||
|
state: () => ({
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default pinia
|
||||||
33
src/store/modules/user.ts
Normal file
33
src/store/modules/user.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 用户相关仓库
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { REMOVE_TOKEN } from '@/utils/token'
|
||||||
|
|
||||||
|
const useUserStore = defineStore(
|
||||||
|
'mihoutaoUser',
|
||||||
|
() => {
|
||||||
|
const userInfo = ref({})
|
||||||
|
const token = ref('')
|
||||||
|
const getUserInfo = async (data: any) => {
|
||||||
|
userInfo.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
const userLogout = async () => {
|
||||||
|
//退出登录请求
|
||||||
|
REMOVE_TOKEN()
|
||||||
|
userInfo.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userInfo,
|
||||||
|
token,
|
||||||
|
getUserInfo,
|
||||||
|
userLogout,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default useUserStore
|
||||||
11
src/styles/element/index.scss
Normal file
11
src/styles/element/index.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// styles/element/index.scss
|
||||||
|
/* 只需要重写你需要的即可 */
|
||||||
|
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||||
|
$colors: (
|
||||||
|
'primary': (
|
||||||
|
'base': #5CC4A7,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
111
src/styles/index.scss
Normal file
111
src/styles/index.scss
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
//引入清除默认样式
|
||||||
|
@import './reset.scss';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
// --my-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1);
|
||||||
|
// --my-shadow-large: 0px 0px 3px 5px var(--el-border-color);
|
||||||
|
// --my-shadow-small: 0px 0px 0px 1px var(--el-border-color);
|
||||||
|
--my-border-radius: 8px;
|
||||||
|
--my-border-radius-large: 16px;
|
||||||
|
--my-border-radius-small: 4px;
|
||||||
|
--el-fill-color-blank: rgba(240, 245, 245, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
//滚动条外观设置
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
width: 10px;
|
||||||
|
background-color: #c4cbd7;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popover.el-popper {
|
||||||
|
border-radius: var(--my-border-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper__arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper.is-light {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-box {
|
||||||
|
// overflow: hidden;
|
||||||
|
margin-top: 25px;
|
||||||
|
text-align: right;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
.el-pagination {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-table {
|
||||||
|
--el-table-header-text-color: var(--el-color-primary) !important;
|
||||||
|
--el-table-bg-color: var(--el-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
--el-select-input-focus-border-color: none !important;
|
||||||
|
--el-input-bg-color: rgba(240, 245, 245, 1) !important;
|
||||||
|
--el-input-focus-border-color: none;
|
||||||
|
--el-input-border-color: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper.is-focus,
|
||||||
|
.el-select__wrapper.is-focused,
|
||||||
|
.el-select__wrapper {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea,
|
||||||
|
.el-date-editor,
|
||||||
|
.el-cascader {
|
||||||
|
--el-input-focus-border-color: none !important;
|
||||||
|
--el-input-bg-color: rgba(240, 245, 245, 1) !important;
|
||||||
|
--el-input-border-color: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper:hover,
|
||||||
|
.el-textarea__inner:hover,
|
||||||
|
.el-input-group__append:hover,
|
||||||
|
.el-input-group__prepend:hover {
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper.is-focus,
|
||||||
|
.el-textarea__inner:focus,
|
||||||
|
.el-date-editor.is-active {
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input-group__append,
|
||||||
|
.el-input-group__prepend {
|
||||||
|
background: var(--el-input-bg-color) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
padding: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.el-textarea,
|
||||||
|
.el-date-editor,
|
||||||
|
.el-cascader {
|
||||||
|
--el-input-focus-border-color: none !important;
|
||||||
|
--el-input-border-color: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-radio{
|
||||||
|
--el-radio-input-bg-color: #fff!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: #fff!important;
|
||||||
|
}
|
||||||
202
src/styles/reset.scss
Normal file
202
src/styles/reset.scss
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
*,
|
||||||
|
*:after,
|
||||||
|
*:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
// min-width: 1300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
div,
|
||||||
|
span,
|
||||||
|
applet,
|
||||||
|
object,
|
||||||
|
iframe,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
blockquote,
|
||||||
|
pre,
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
big,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
del,
|
||||||
|
dfn,
|
||||||
|
em,
|
||||||
|
img,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
q,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
small,
|
||||||
|
strike,
|
||||||
|
strong,
|
||||||
|
sub,
|
||||||
|
sup,
|
||||||
|
tt,
|
||||||
|
var,
|
||||||
|
b,
|
||||||
|
u,
|
||||||
|
i,
|
||||||
|
center,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
dd,
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
li,
|
||||||
|
fieldset,
|
||||||
|
form,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
table,
|
||||||
|
caption,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
thead,
|
||||||
|
tr,
|
||||||
|
th,
|
||||||
|
td,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
canvas,
|
||||||
|
details,
|
||||||
|
embed,
|
||||||
|
figure,
|
||||||
|
figcaption,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
output,
|
||||||
|
ruby,
|
||||||
|
section,
|
||||||
|
summary,
|
||||||
|
time,
|
||||||
|
mark,
|
||||||
|
audio,
|
||||||
|
video {
|
||||||
|
// font: inherit;
|
||||||
|
|
||||||
|
font-size: 100%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
vertical-align: baseline;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
q {
|
||||||
|
quotes: none;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
button {
|
||||||
|
// font-family: inhert;
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
text-indent: .01px;
|
||||||
|
text-overflow: '';
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
--el-text-color-regular: #606266;
|
||||||
|
}
|
||||||
25
src/utils/fitter.js
Normal file
25
src/utils/fitter.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 计算公式当前屏幕宽度winWidth除以设计稿宽度1920得到屏幕与设计稿的比例,根据比例设置根元素的fontSize,设计稿的100px等于1rem
|
||||||
|
|
||||||
|
// 获取屏幕宽度
|
||||||
|
var winWidth = document.documentElement.offsetWidth || document.body.offsetWidth
|
||||||
|
// 获取html跟元素
|
||||||
|
var oHtml = document.getElementsByTagName('html')[0]
|
||||||
|
// oHtml.style.fontSize = 100 * winWidth / 1680 + 'px'
|
||||||
|
if(winWidth > 1680){
|
||||||
|
oHtml.style.fontSize = 100 * 1680 / 1680 + 'px'
|
||||||
|
} else {
|
||||||
|
oHtml.style.fontSize = 100 * winWidth / 1680 + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面大小发生变化事重新设置
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
var winWidth = document.documentElement.offsetWidth || document.body.offsetWidth
|
||||||
|
var oHtml = document.getElementsByTagName('html')[0]
|
||||||
|
|
||||||
|
if(winWidth > 1680){
|
||||||
|
oHtml.style.fontSize = 100 * 1680 / 1680 + 'px'
|
||||||
|
} else {
|
||||||
|
oHtml.style.fontSize = 100 * winWidth / 1680 + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
55
src/utils/index.ts
Normal file
55
src/utils/index.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* @description 生成随机数
|
||||||
|
* @param {Number} min 最小值
|
||||||
|
* @param {Number} max 最大值
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
export function randomNum(min: number, max: number): number {
|
||||||
|
const num = Math.floor(Math.random() * (min - max) + max)
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rgba转#
|
||||||
|
* @param color 颜色
|
||||||
|
*/
|
||||||
|
export function getHexColor(color: string) {
|
||||||
|
const values = color
|
||||||
|
.replace(/rgba?\(/, '')
|
||||||
|
.replace(/\)/, '')
|
||||||
|
.replace(/[\s+]/g, '')
|
||||||
|
.split(',')
|
||||||
|
const a = parseFloat(values[3]),
|
||||||
|
r = Math.floor(a * parseInt(values[0]) + (1 - a) * 255),
|
||||||
|
g = Math.floor(a * parseInt(values[1]) + (1 - a) * 255),
|
||||||
|
b = Math.floor(a * parseInt(values[2]) + (1 - a) * 255)
|
||||||
|
return '#' +
|
||||||
|
('0' + r.toString(16)).slice(-2) +
|
||||||
|
('0' + g.toString(16)).slice(-2) +
|
||||||
|
('0' + b.toString(16)).slice(-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Component的方法
|
||||||
|
* @param view 路径
|
||||||
|
* @param type 路由层数 目前只兼容二级路由
|
||||||
|
*/
|
||||||
|
export function _getViews(view: any, type: any) {
|
||||||
|
let res;
|
||||||
|
let modules: any;
|
||||||
|
if (type == "one") {
|
||||||
|
modules = import.meta.glob("/src/view/*.vue");
|
||||||
|
} else {
|
||||||
|
modules = import.meta.glob("/src/view/**/*.vue");
|
||||||
|
}
|
||||||
|
for (const path in modules) {
|
||||||
|
const dir =
|
||||||
|
type == "one"
|
||||||
|
? path.split("view/")[1].split(".vue")[0]
|
||||||
|
: path.split("view/")[1].split(".vue")[0];
|
||||||
|
if (dir === view) {
|
||||||
|
res = () => modules[path]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
68
src/utils/request.ts
Normal file
68
src/utils/request.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElNotification } from 'element-plus'
|
||||||
|
import { GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import pinia from '@/store'
|
||||||
|
//创建axios实例
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||||
|
timeout: 120000,
|
||||||
|
})
|
||||||
|
// 存储需要取消的请求
|
||||||
|
const requestsToCancel = {};
|
||||||
|
|
||||||
|
//请求拦截器
|
||||||
|
request.interceptors.request.use((config: any) => {
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
config.headers.Authorization = GET_TOKEN()
|
||||||
|
}
|
||||||
|
// 为每个请求生成一个唯一的cancel token
|
||||||
|
config.cancelToken = new axios.CancelToken(cancel => {
|
||||||
|
requestsToCancel[config.url] = cancel;
|
||||||
|
});
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
//响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
(response: any) => {
|
||||||
|
return response.data
|
||||||
|
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
//处理网络错误
|
||||||
|
if (error.response?.data.code == 20107) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if (error.response?.data.code == 10101) {
|
||||||
|
// Object.values(requestsToCancel).forEach(cancel => cancel());
|
||||||
|
// }
|
||||||
|
if (error.response?.data.code == 10103) {
|
||||||
|
Object.values(requestsToCancel).forEach(cancel => cancel());
|
||||||
|
const useUser = useUserStore(pinia)
|
||||||
|
useUser.userInfo = {}
|
||||||
|
useUser.token = ''
|
||||||
|
REMOVE_TOKEN()
|
||||||
|
ElNotification({
|
||||||
|
type: 'error',
|
||||||
|
message: error.response.data.message,
|
||||||
|
duration: 2000,
|
||||||
|
onClose: () => {
|
||||||
|
if(location.pathname != '/index'){
|
||||||
|
location.href = '/index'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElNotification({
|
||||||
|
type: 'error',
|
||||||
|
message: error.response.data.message,
|
||||||
|
duration: 2000,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export default request
|
||||||
12
src/utils/time.ts
Normal file
12
src/utils/time.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const getTime = () => {
|
||||||
|
const hours = new Date().getHours()
|
||||||
|
if (hours <= 9) {
|
||||||
|
return '早上好 🌅'
|
||||||
|
} else if (hours <= 12) {
|
||||||
|
return '上午好 🌞'
|
||||||
|
} else if (hours <= 18) {
|
||||||
|
return '下午好 ☕️'
|
||||||
|
} else {
|
||||||
|
return '晚上好 🌛'
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/utils/token.ts
Normal file
18
src/utils/token.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const SET_TOKEN = (token: string) => {
|
||||||
|
localStorage.setItem('TOKEN', token)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const GET_TOKEN = () => {
|
||||||
|
return localStorage.getItem('TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const REMOVE_TOKEN = () => {
|
||||||
|
localStorage.removeItem('TOKEN')
|
||||||
|
}
|
||||||
|
export const SET_USER_TYPE = (type) => {
|
||||||
|
localStorage.removeItem('userType')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
42
src/utils/validate.ts
Normal file
42
src/utils/validate.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// 电话校验
|
||||||
|
export const checkPhoneNumber = (rule: any, value: any, callback: any) => {
|
||||||
|
const reg = /^[1][3456789][0-9]{9}$/
|
||||||
|
if ((reg.test(value))) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
callback(new Error('validate.please_enter_the_correct_phone_number'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 验证邮箱的规则
|
||||||
|
export const checkEmail = (rule: any, value: any, callback: any) => {
|
||||||
|
// 验证邮箱的正则表达式
|
||||||
|
const reg = /^[a-zA-Z0-9]+([\.-_][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([\.-][a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/
|
||||||
|
if (reg.test(value)) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
callback(new Error('请输入正确的的邮箱'))
|
||||||
|
}
|
||||||
|
// 密码规则
|
||||||
|
export const checkPassword= (rule: any, value: any, callback: any) => {
|
||||||
|
|
||||||
|
const reg = /^[a-zA-Z0-9\W]{6,10}$/
|
||||||
|
if (reg.test(value)) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
callback(new Error('你输入6到10位密码, 不能包含特殊符号'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字验证
|
||||||
|
export const checkNumber = (rule: any, value: any, callback: any) => {
|
||||||
|
if(isNaN(value)){
|
||||||
|
callback(new Error('validate.please_enter_a_number_greater_than_0'))
|
||||||
|
} else {
|
||||||
|
if (value > 0) {
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
callback(new Error('validate.please_enter_a_number_greater_than_0'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/views/404/index.vue
Normal file
52
src/views/404/index.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
let $router = useRouter();
|
||||||
|
const goHome = () => {
|
||||||
|
$router.push('/index')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="box">
|
||||||
|
<img src="../../assets/images/error_images/404.png" />
|
||||||
|
<div>
|
||||||
|
<h2>您访问的页面不存在</h2>
|
||||||
|
<p>请检查您的地址是否正确,或者点击下面按钮回到首页</p>
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" round @click="goHome">返回首页</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.box {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 800px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: gray;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 20px 0 50px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
769
src/views/adminPages/AICreation/index.vue
Normal file
769
src/views/adminPages/AICreation/index.vue
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="ai-box">
|
||||||
|
|
||||||
|
<div class="editor-box">
|
||||||
|
<div class="toolbar-box">
|
||||||
|
<Toolbar style="" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="title-box">
|
||||||
|
<input type="text" v-model="newTitle" class="title-input" placeholder="请输入文章标题" />
|
||||||
|
</div>
|
||||||
|
<Editor class="editor" style="height: calc(100% - 190px); overflow-y: hidden;" v-model="valueHtml"
|
||||||
|
:defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated" />
|
||||||
|
|
||||||
|
<!-- 添加字数统计显示 -->
|
||||||
|
|
||||||
|
|
||||||
|
<div class="editor-btn-box">
|
||||||
|
<div class="character-count">
|
||||||
|
字数: {{ characterCount }}
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" @click="handleSaveArticle(1)">保存</el-button>
|
||||||
|
<el-button type="primary" @click="handleSaveArticle(2)">存草稿</el-button>
|
||||||
|
<el-button type="primary" @click="handlePreviewArticle">预览</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogPreview" title="文章预览" width="1000px">
|
||||||
|
<div class="dialog-preview" style="padding: 20px;">
|
||||||
|
<div class="preview-title">{{ newTitle }}</div>
|
||||||
|
<div class="preview-content" v-html="valueHtml">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="dialogPreview = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||||
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
|
import { onBeforeUnmount, onUnmounted , ref, reactive, shallowRef, onMounted, computed, getCurrentInstance, nextTick } from 'vue'
|
||||||
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
|
|
||||||
|
import { title, inspiration, configList, writeOutline, models, generateArticle, analysisStyle, saveStyle, saveArticle, editArticle, customerTool } from '@/api/AICreation'
|
||||||
|
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const generating = ref(1)
|
||||||
|
// 编辑器实例,必须用 shallowRef
|
||||||
|
const editorRef = shallowRef()
|
||||||
|
|
||||||
|
// 内容 HTML
|
||||||
|
const valueHtml = ref('')
|
||||||
|
const mode = ref('default')
|
||||||
|
// 纯文本
|
||||||
|
const valueText = ref('')
|
||||||
|
// 添加字数统计
|
||||||
|
|
||||||
|
const characterCount = ref(0)
|
||||||
|
const toolbarConfig = {}
|
||||||
|
const editorConfig = {
|
||||||
|
placeholder: '请输入内容...',
|
||||||
|
MENU_CONF: {}, // Initialize MENU_CONF as an empty object
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
editorConfig.MENU_CONF['uploadImage'] = {
|
||||||
|
// 上传图片的配置
|
||||||
|
server: import.meta.env.VITE_API_BASE_URL + '/api/upload', // 使用环境变量中的API基础URL
|
||||||
|
// 上传图片的最大体积限制,默认为 10M
|
||||||
|
maxFileSize: 5 * 1024 * 1024, // 5M
|
||||||
|
// 上传图片的类型限制
|
||||||
|
allowedFileTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||||
|
// 自定义上传参数
|
||||||
|
meta: {
|
||||||
|
token: localStorage.getItem('token') || ''
|
||||||
|
},
|
||||||
|
// 自定义添加 http 请求头
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + (localStorage.getItem('token') || '')
|
||||||
|
},
|
||||||
|
// 上传之前触发
|
||||||
|
onBeforeUpload(file) {
|
||||||
|
console.log('准备上传图片', file)
|
||||||
|
return file // 返回file继续上传
|
||||||
|
},
|
||||||
|
// 上传成功触发
|
||||||
|
onSuccess(file, res) {
|
||||||
|
console.log('图片上传成功', file, res)
|
||||||
|
},
|
||||||
|
// 上传失败触发
|
||||||
|
onError(file, err, res) {
|
||||||
|
console.log('图片上传失败', file, err, res)
|
||||||
|
// 可以自定义错误提示
|
||||||
|
ElNotification.error('图片上传失败,请重试')
|
||||||
|
},
|
||||||
|
// 自定义插入图片
|
||||||
|
customInsert(res, insertFn) {
|
||||||
|
// res 即服务端的返回结果
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
// 从响应结果中获取图片URL
|
||||||
|
const url = res.data.url || res.data
|
||||||
|
// 调用插入图片的函数
|
||||||
|
insertFn(url)
|
||||||
|
} else {
|
||||||
|
ElNotification.error('图片插入失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editorConfig.MENU_CONF['uploadVideo'] = {
|
||||||
|
server: import.meta.env.VITE_API_BASE_URL + '/api/upload', // 使用环境变量中的API基础URL
|
||||||
|
maxFileSize: 5 * 1024 * 1024, // 5M
|
||||||
|
onBeforeUpload: (file) => {
|
||||||
|
console.log('准备上传视频', file)
|
||||||
|
return file // 返回file继续上传
|
||||||
|
},
|
||||||
|
onSuccess: (file, res) => {
|
||||||
|
console.log('视频上传成功', file, res)
|
||||||
|
},
|
||||||
|
onError: (file, err, res) => {
|
||||||
|
console.log('视频上传失败', file, err, res)
|
||||||
|
ElNotification.error('视频上传失败,请重试')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 组件销毁时,也及时销毁编辑器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor == null) return
|
||||||
|
editor.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreated = (editor) => {
|
||||||
|
editorRef.value = editor // 记录 editor 实例,重要!
|
||||||
|
if (route.query.id) {
|
||||||
|
editor.setHtml(sessionStorage.getItem('works'))
|
||||||
|
// valueHtml.value = sessionStorage.getItem('works')
|
||||||
|
}
|
||||||
|
// 监听编辑器内容变化,可以在这里添加额外逻辑
|
||||||
|
editor.on('change', (e) => {
|
||||||
|
// 获取纯文本
|
||||||
|
valueText.value = editor.getText()
|
||||||
|
// 计算字数
|
||||||
|
characterCount.value = valueText.value.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const newTitle = ref('')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auto-resize directive
|
||||||
|
const vAutoResize = {
|
||||||
|
mounted: (el) => {
|
||||||
|
const resize = () => {
|
||||||
|
el.style.height = 'auto'
|
||||||
|
el.style.height = `${el.scrollHeight}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial resize
|
||||||
|
resize()
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
el.addEventListener('input', resize)
|
||||||
|
window.addEventListener('resize', resize)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
el.removeEventListener('input', resize)
|
||||||
|
window.removeEventListener('resize', resize)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register directive
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
app.appContext.app.directive('auto-resize', vAutoResize)
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.toolbar-box{
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
:deep(.el-radio__label) {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
// margin: 20px 0;
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.editor-box {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 60px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--el-border-color);
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
height: calc(100% - 300px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn-box {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 15px;
|
||||||
|
text-align: right;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-box {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.title-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-btn {
|
||||||
|
flex: none;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-box {
|
||||||
|
width: 350px;
|
||||||
|
flex: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.tab-item-label {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-btn {
|
||||||
|
// margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-radio-box {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
height: 160px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.el-radio-group) {
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-radio) {
|
||||||
|
display: flex;
|
||||||
|
height: auto;
|
||||||
|
line-height: 18px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-box {
|
||||||
|
margin-top: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
padding: 15px 20px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
// border: 1px solid var(--el-border-color);
|
||||||
|
cursor: pointer;
|
||||||
|
// border-radius: 4px;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// margin-bottom: 10px;
|
||||||
|
span {
|
||||||
|
color: rgb(239, 53, 53);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 15px;
|
||||||
|
font-style: italic;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-select-box {
|
||||||
|
.el-select {
|
||||||
|
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-text {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content-title {
|
||||||
|
// height: 200px;
|
||||||
|
min-height: 200px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 20px 15px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 20px;
|
||||||
|
padding-right: 50px;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content-title-span {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.serial-number {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
color: var(--el-color-primary-light-5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-title-text {
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 15px;
|
||||||
|
color: var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-item {
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: -10px -8px 0 -2px var(--el-color-primary), 0 -8px 0 -1px var(--el-color-primary-light-5), 10px -8px 0 -2px var(--el-color-primary-light-5);
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
height: 8px;
|
||||||
|
margin: 0 10px;
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(calc(100% - 1px)) translateX(8px);
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-drawer__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-drawer__body) {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content, .drawer-content2 {
|
||||||
|
padding: 20px;
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
height: calc(100vh - 142px);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.drawer-content-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
.drawer-content-item-button {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-enter-active,
|
||||||
|
.expand-leave-active {
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
max-height: 50px;
|
||||||
|
/* 根据实际内容调整最大高度 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-enter-from,
|
||||||
|
.expand-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-item-box {
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #f9f9fb;
|
||||||
|
padding: 18px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.streaming {
|
||||||
|
background-color: rgba(var(--el-color-primary-rgb), 0.05);
|
||||||
|
border: 1px solid rgba(var(--el-color-primary-rgb), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streaming-cursor {
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
bottom: 15px;
|
||||||
|
width: 8px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.EaFdC {
|
||||||
|
// list-style: cjk-ideographic;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-title {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
// list-style: cjk-ideographic;
|
||||||
|
margin: 0;
|
||||||
|
// margin-left: 35px;
|
||||||
|
height: auto;
|
||||||
|
min-height: 24px;
|
||||||
|
|
||||||
|
&::marker {
|
||||||
|
unicode-bidi: isolate;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
text-transform: none;
|
||||||
|
text-indent: 0px !important;
|
||||||
|
text-align: start !important;
|
||||||
|
text-align-last: start !important;
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0);
|
||||||
|
border: 0;
|
||||||
|
font-style: normal;
|
||||||
|
resize: none;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
top: 7px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0);
|
||||||
|
border: 0;
|
||||||
|
font-style: normal;
|
||||||
|
resize: none;
|
||||||
|
height: auto;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.generating-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
left: 30px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots {
|
||||||
|
animation: loadingDots 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loadingDots {
|
||||||
|
0% { opacity: 0.2; }
|
||||||
|
20% { opacity: 1; }
|
||||||
|
100% { opacity: 0.2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview {
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
|
.article-preview-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview-content {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
// max-height: calc(100vh - 200px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f9f9fb;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extract-style {
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
// min-height: 100px;
|
||||||
|
// max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
pre {
|
||||||
|
line-height: 18px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-click {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview-title {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-preview {
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
// p{
|
||||||
|
// line-height: 26px;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawer-footer{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
128
src/views/adminPages/hot/index.vue
Normal file
128
src/views/adminPages/hot/index.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="hot-search">
|
||||||
|
<div class="hot-search-type">
|
||||||
|
<el-button :type="activeType === 'ai' ? 'primary' : 'default'" @click="handleFilter('ai')">AI
|
||||||
|
热榜</el-button>
|
||||||
|
<el-button :type="activeType === 'low' ? 'primary' : 'default'"
|
||||||
|
@click="handleFilter('low')">低粉高爆</el-button>
|
||||||
|
<el-button :type="activeType === 'create' ? 'primary' : 'default'"
|
||||||
|
@click="handleFilter('create')">创作榜单</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="hot-search-filter">
|
||||||
|
<span>关键词</span>
|
||||||
|
<el-input v-model="query.keyword" placeholder="关键词搜索" style="width: 200px;" />
|
||||||
|
<span>阅读量</span>
|
||||||
|
<el-select v-model="query.readCount" placeholder="请选择阅读量" style="width: 200px;">
|
||||||
|
<el-option label="1000-10000" value="1000-10000"></el-option>
|
||||||
|
<el-option label="10000-100000" value="10000-100000"></el-option>
|
||||||
|
<el-option label="100000-1000000" value="100000-1000000"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<span>发布时间</span>
|
||||||
|
<el-date-picker style="width: 300px;" v-model="date" type="daterange" range-separator="至"
|
||||||
|
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD"
|
||||||
|
@change="handleDateChange" />
|
||||||
|
<el-button type="primary" @click="handleFilter">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<el-table :data="tableData" style="width: 100%;">
|
||||||
|
<el-table-column label="序号" type="index" width="80" />
|
||||||
|
<el-table-column prop="title" label="标题" />
|
||||||
|
<el-table-column prop="" label="领域" />
|
||||||
|
<el-table-column prop="readCount" label="阅读量" />
|
||||||
|
<el-table-column prop="publishTime" label="发布时间" />
|
||||||
|
<el-table-column prop="action" label="操作" width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-link type="primary" @click="handleEdit(scope.row)">AI 改写</el-link>
|
||||||
|
<el-link type="primary" @click="handleEdit(scope.row)">查看原文</el-link>
|
||||||
|
<el-link type="primary" @click="handleEdit(scope.row)">创作灵感</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-pagination background v-model:current-page="query.page" v-model:page-size="query.pageSize"
|
||||||
|
:page-sizes="[100, 200, 300, 400]" :size="query.pageSize" :disabled="disabled" :background="background"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper" :total="400" @size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
|
||||||
|
const activeType = ref('ai');
|
||||||
|
|
||||||
|
const handleFilter = (type) => {
|
||||||
|
activeType.value = type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
keyword: '',
|
||||||
|
readCount: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDateChange = (value) => {
|
||||||
|
query.startDate = value[0];
|
||||||
|
query.endDate = value[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pageSize.value = size;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
query.page = page;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handleFilter(activeType.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.hot-search {
|
||||||
|
|
||||||
|
.hot-search-type {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-search-filter {
|
||||||
|
line-height: 36px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-link+ .el-link {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/views/adminPages/works/index.vue
Normal file
8
src/views/adminPages/works/index.vue
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>热榜</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
63
src/views/home/index.vue
Normal file
63
src/views/home/index.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getTime } from '@/utils/time';
|
||||||
|
//引入用户相关的仓库,获取当前用户的头像、昵称
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
//获取存储用户信息的仓库对象
|
||||||
|
let userStore = useUserStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-card>
|
||||||
|
<div class="box">
|
||||||
|
<div class="bottom">
|
||||||
|
<!-- <h3 class="title">{{ getTime() }} {{ userStore.username }} </h3> -->
|
||||||
|
<p class="subtitle">{{ $t('common.welcome_to_use') }} {{ $t('common.carbonease') }} CarbonEasy</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<div class="bottoms">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
background: url(../../assets/images/welcome.png) no-repeat center 60%;
|
||||||
|
background-size: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 30px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 900;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bottoms{
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
src/views/index/index.vue
Normal file
88
src/views/index/index.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { GET_TOKEN } from '@/utils/token';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { accountImages } from '@/api/group';
|
||||||
|
import emitter from '@/eventBus'
|
||||||
|
import Header from '@/layout/header/index.vue'
|
||||||
|
const router = useRouter()
|
||||||
|
const imageList = ref([])
|
||||||
|
|
||||||
|
const toDash = () => {
|
||||||
|
if (GET_TOKEN()) {
|
||||||
|
router.push('/dash')
|
||||||
|
} else {
|
||||||
|
emitter.emit('userLogin', 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getAccoutImages = async () => {
|
||||||
|
const res = await accountImages()
|
||||||
|
imageList.value = res.list
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
getAccoutImages()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<p>一站式公众号订阅</p>
|
||||||
|
|
||||||
|
<div class="start-use">
|
||||||
|
<button @click="toDash">开始使用</button>
|
||||||
|
</div>
|
||||||
|
<div class="account-image">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="4" v-for="item in imageList"><img :src="item.hd_head_img" /></el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
padding-top: 200px;
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 18px;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 60px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.start-use {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 50px;
|
||||||
|
button{
|
||||||
|
width: 220px;
|
||||||
|
height: 60px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 22px;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--el-border-radius-base);
|
||||||
|
&:hover{
|
||||||
|
background-color: var(--el-color-primary-light-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.account-image{
|
||||||
|
width: 75%;
|
||||||
|
margin: 100px auto;
|
||||||
|
img{
|
||||||
|
display: block;
|
||||||
|
width: 110px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 50px;
|
||||||
|
border-radius: 100px;
|
||||||
|
box-shadow: var(--el-box-shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
112
src/views/login/index.vue
Normal file
112
src/views/login/index.vue
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<el-card class="login-card">
|
||||||
|
<template #header>
|
||||||
|
<h2 class="login-title">系统登录</h2>
|
||||||
|
</template>
|
||||||
|
<el-form
|
||||||
|
ref="loginFormRef"
|
||||||
|
:model="loginForm"
|
||||||
|
:rules="loginRules"
|
||||||
|
label-width="0"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input
|
||||||
|
v-model="loginForm.username"
|
||||||
|
placeholder="用户名"
|
||||||
|
prefix-icon="User"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="password"
|
||||||
|
placeholder="密码"
|
||||||
|
prefix-icon="Lock"
|
||||||
|
show-password
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
class="login-button"
|
||||||
|
@click="handleLogin"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loginFormRef = ref(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const loginForm = reactive({
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginRules = {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
if (!loginFormRef.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await loginFormRef.value.validate()
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
// TODO: 这里添加实际的登录API调用
|
||||||
|
// const res = await login(loginForm)
|
||||||
|
localStorage.setItem('TOKEN', '123456')
|
||||||
|
// 模拟登录成功
|
||||||
|
setTimeout(() => {
|
||||||
|
ElMessage.success('登录成功')
|
||||||
|
router.push('/')
|
||||||
|
loading.value = false
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('登录失败,请检查用户名和密码')
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.login-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
760
src/views/news/addNew.vue
Normal file
760
src/views/news/addNew.vue
Normal file
@ -0,0 +1,760 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="ai-box">
|
||||||
|
|
||||||
|
<div class="editor-box">
|
||||||
|
<div class="toolbar-box">
|
||||||
|
<Toolbar style="" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="title-box">
|
||||||
|
<input type="text" v-model="newTitle" class="title-input" placeholder="请输入文章标题" />
|
||||||
|
</div>
|
||||||
|
<Editor class="editor" style="height: calc(100% - 190px); overflow-y: hidden;" v-model="valueHtml"
|
||||||
|
:defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated" />
|
||||||
|
|
||||||
|
<!-- 添加字数统计显示 -->
|
||||||
|
|
||||||
|
|
||||||
|
<div class="editor-btn-box">
|
||||||
|
<div class="character-count">
|
||||||
|
字数: {{ characterCount }}
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" @click="handleSaveArticle(1)">保存</el-button>
|
||||||
|
<el-button type="primary" @click="handleSaveArticle(2)">存草稿</el-button>
|
||||||
|
<el-button type="primary" @click="handlePreviewArticle">预览</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogPreview" title="文章预览" width="1000px">
|
||||||
|
<div class="dialog-preview" style="padding: 20px;">
|
||||||
|
<div class="preview-title">{{ newTitle }}</div>
|
||||||
|
<div class="preview-content" v-html="valueHtml">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="dialogPreview = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||||
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
|
import { onBeforeUnmount, onUnmounted , ref, reactive, shallowRef, onMounted, computed, getCurrentInstance, nextTick } from 'vue'
|
||||||
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
|
|
||||||
|
// import { title, inspiration, configList, writeOutline, models, generateArticle, analysisStyle, saveStyle, saveArticle, editArticle, customerTool } from '@/api/AICreation'
|
||||||
|
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const generating = ref(1)
|
||||||
|
// 编辑器实例,必须用 shallowRef
|
||||||
|
const editorRef = shallowRef()
|
||||||
|
|
||||||
|
// 内容 HTML
|
||||||
|
const valueHtml = ref('')
|
||||||
|
const mode = ref('default')
|
||||||
|
// 纯文本
|
||||||
|
const valueText = ref('')
|
||||||
|
// 添加字数统计
|
||||||
|
|
||||||
|
const characterCount = ref(0)
|
||||||
|
const toolbarConfig = {}
|
||||||
|
const editorConfig = {
|
||||||
|
placeholder: '请输入内容...',
|
||||||
|
MENU_CONF: {}, // Initialize MENU_CONF as an empty object
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
editorConfig.MENU_CONF['uploadImage'] = {
|
||||||
|
// 上传图片的配置
|
||||||
|
server: import.meta.env.VITE_API_BASE_URL + '/api/upload', // 使用环境变量中的API基础URL
|
||||||
|
// 上传图片的最大体积限制,默认为 10M
|
||||||
|
maxFileSize: 5 * 1024 * 1024, // 5M
|
||||||
|
// 上传图片的类型限制
|
||||||
|
allowedFileTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||||
|
// 自定义上传参数
|
||||||
|
meta: {
|
||||||
|
token: localStorage.getItem('token') || ''
|
||||||
|
},
|
||||||
|
// 自定义添加 http 请求头
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + (localStorage.getItem('token') || '')
|
||||||
|
},
|
||||||
|
// 上传之前触发
|
||||||
|
onBeforeUpload(file) {
|
||||||
|
console.log('准备上传图片', file)
|
||||||
|
return file // 返回file继续上传
|
||||||
|
},
|
||||||
|
// 上传成功触发
|
||||||
|
onSuccess(file, res) {
|
||||||
|
console.log('图片上传成功', file, res)
|
||||||
|
},
|
||||||
|
// 上传失败触发
|
||||||
|
onError(file, err, res) {
|
||||||
|
console.log('图片上传失败', file, err, res)
|
||||||
|
// 可以自定义错误提示
|
||||||
|
ElNotification.error('图片上传失败,请重试')
|
||||||
|
},
|
||||||
|
// 自定义插入图片
|
||||||
|
customInsert(res, insertFn) {
|
||||||
|
// res 即服务端的返回结果
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
// 从响应结果中获取图片URL
|
||||||
|
const url = res.data.url || res.data
|
||||||
|
// 调用插入图片的函数
|
||||||
|
insertFn(url)
|
||||||
|
} else {
|
||||||
|
ElNotification.error('图片插入失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editorConfig.MENU_CONF['uploadVideo'] = {
|
||||||
|
server: import.meta.env.VITE_API_BASE_URL + '/api/upload', // 使用环境变量中的API基础URL
|
||||||
|
maxFileSize: 5 * 1024 * 1024, // 5M
|
||||||
|
onBeforeUpload: (file) => {
|
||||||
|
console.log('准备上传视频', file)
|
||||||
|
return file // 返回file继续上传
|
||||||
|
},
|
||||||
|
onSuccess: (file, res) => {
|
||||||
|
console.log('视频上传成功', file, res)
|
||||||
|
},
|
||||||
|
onError: (file, err, res) => {
|
||||||
|
console.log('视频上传失败', file, err, res)
|
||||||
|
ElNotification.error('视频上传失败,请重试')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 组件销毁时,也及时销毁编辑器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
if (editor == null) return
|
||||||
|
editor.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreated = (editor) => {
|
||||||
|
editorRef.value = editor // 记录 editor 实例,重要!
|
||||||
|
if (route.query.id) {
|
||||||
|
editor.setHtml(sessionStorage.getItem('works'))
|
||||||
|
// valueHtml.value = sessionStorage.getItem('works')
|
||||||
|
}
|
||||||
|
// 监听编辑器内容变化,可以在这里添加额外逻辑
|
||||||
|
editor.on('change', (e) => {
|
||||||
|
// 获取纯文本
|
||||||
|
valueText.value = editor.getText()
|
||||||
|
// 计算字数
|
||||||
|
characterCount.value = valueText.value.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const newTitle = ref('')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auto-resize directive
|
||||||
|
const vAutoResize = {
|
||||||
|
mounted: (el) => {
|
||||||
|
const resize = () => {
|
||||||
|
el.style.height = 'auto'
|
||||||
|
el.style.height = `${el.scrollHeight}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial resize
|
||||||
|
resize()
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
el.addEventListener('input', resize)
|
||||||
|
window.addEventListener('resize', resize)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
el.removeEventListener('input', resize)
|
||||||
|
window.removeEventListener('resize', resize)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register directive
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
app.appContext.app.directive('auto-resize', vAutoResize)
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.toolbar-box{
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
:deep(.el-radio__label) {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
// margin: 20px 0;
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.editor-box {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
height: calc(100% - 300px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn-box {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-count {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 15px;
|
||||||
|
text-align: right;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-box {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.title-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-btn {
|
||||||
|
flex: none;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-box {
|
||||||
|
width: 350px;
|
||||||
|
flex: none;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.tab-item-label {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-btn {
|
||||||
|
// margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-radio-box {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
height: 160px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.el-radio-group) {
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-radio) {
|
||||||
|
display: flex;
|
||||||
|
height: auto;
|
||||||
|
line-height: 18px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-box {
|
||||||
|
margin-top: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
padding: 15px 20px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
// border: 1px solid var(--el-border-color);
|
||||||
|
cursor: pointer;
|
||||||
|
// border-radius: 4px;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// margin-bottom: 10px;
|
||||||
|
span {
|
||||||
|
color: rgb(239, 53, 53);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 15px;
|
||||||
|
font-style: italic;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-item {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-select-box {
|
||||||
|
.el-select {
|
||||||
|
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item-text {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content-title {
|
||||||
|
// height: 200px;
|
||||||
|
min-height: 200px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 20px 15px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 20px;
|
||||||
|
padding-right: 50px;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content-title-span {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.serial-number {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
color: var(--el-color-primary-light-5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-title-text {
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 15px;
|
||||||
|
color: var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-item {
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: -10px -8px 0 -2px var(--el-color-primary), 0 -8px 0 -1px var(--el-color-primary-light-5), 10px -8px 0 -2px var(--el-color-primary-light-5);
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
height: 8px;
|
||||||
|
margin: 0 10px;
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(calc(100% - 1px)) translateX(8px);
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-drawer__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-drawer__body) {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content, .drawer-content2 {
|
||||||
|
padding: 20px;
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
height: calc(100vh - 142px);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.drawer-content-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
.drawer-content-item-button {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-enter-active,
|
||||||
|
.expand-leave-active {
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
max-height: 50px;
|
||||||
|
/* 根据实际内容调整最大高度 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-enter-from,
|
||||||
|
.expand-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-item-box {
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #f9f9fb;
|
||||||
|
padding: 18px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.streaming {
|
||||||
|
background-color: rgba(var(--el-color-primary-rgb), 0.05);
|
||||||
|
border: 1px solid rgba(var(--el-color-primary-rgb), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streaming-cursor {
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
bottom: 15px;
|
||||||
|
width: 8px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.EaFdC {
|
||||||
|
// list-style: cjk-ideographic;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-title {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
// list-style: cjk-ideographic;
|
||||||
|
margin: 0;
|
||||||
|
// margin-left: 35px;
|
||||||
|
height: auto;
|
||||||
|
min-height: 24px;
|
||||||
|
|
||||||
|
&::marker {
|
||||||
|
unicode-bidi: isolate;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
text-transform: none;
|
||||||
|
text-indent: 0px !important;
|
||||||
|
text-align: start !important;
|
||||||
|
text-align-last: start !important;
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0);
|
||||||
|
border: 0;
|
||||||
|
font-style: normal;
|
||||||
|
resize: none;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
top: 7px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0);
|
||||||
|
border: 0;
|
||||||
|
font-style: normal;
|
||||||
|
resize: none;
|
||||||
|
height: auto;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.generating-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
left: 30px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots {
|
||||||
|
animation: loadingDots 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loadingDots {
|
||||||
|
0% { opacity: 0.2; }
|
||||||
|
20% { opacity: 1; }
|
||||||
|
100% { opacity: 0.2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview {
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
|
.article-preview-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview-content {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
// max-height: calc(100vh - 200px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f9f9fb;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extract-style {
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: var(--el-fill-color-blank);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
// min-height: 100px;
|
||||||
|
// max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
pre {
|
||||||
|
line-height: 18px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-click {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview-title {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-preview {
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
// p{
|
||||||
|
// line-height: 26px;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawer-footer{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
src/views/news/index.vue
Normal file
48
src/views/news/index.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="user-search">
|
||||||
|
<el-input style="width: 200px;margin-right: 10px;" v-model="search" placeholder="请输入文章标题" />
|
||||||
|
<el-button type="primary">搜索</el-button>
|
||||||
|
<el-button style="float: right;" type="primary" @click="addNews">添加文章</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="user-table">
|
||||||
|
<el-table :data="tableData" style="width: 100%">
|
||||||
|
<el-table-column prop="title" label="文章标题" />
|
||||||
|
<el-table-column prop="author" label="作者" />
|
||||||
|
<el-table-column prop="createTime" label="创建时间" />
|
||||||
|
<el-table-column prop="updateTime" label="更新时间" />
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="prev, pager, next"
|
||||||
|
:total="1000"
|
||||||
|
:page-size="20"
|
||||||
|
:current-page="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
const router = useRouter()
|
||||||
|
const tableData = ref([
|
||||||
|
{
|
||||||
|
title: '文章标题',
|
||||||
|
author: '张三',
|
||||||
|
createTime: '2021-01-01',
|
||||||
|
updateTime: '2021-01-01',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const search = ref('')
|
||||||
|
const addNews = () => {
|
||||||
|
router.push('/addNew')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-search {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
40
src/views/user/index.vue
Normal file
40
src/views/user/index.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="user-search">
|
||||||
|
<el-input style="width: 200px;margin-right: 10px;" v-model="search" placeholder="请输入患者姓名" />
|
||||||
|
<el-button type="primary">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="user-table">
|
||||||
|
<el-table :data="tableData" style="width: 100%">
|
||||||
|
<el-table-column prop="name" label="姓名" />
|
||||||
|
<el-table-column prop="phone" label="手机号" />
|
||||||
|
<el-table-column prop="email" label="邮箱" />
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="prev, pager, next"
|
||||||
|
:total="1000"
|
||||||
|
:page-size="20"
|
||||||
|
:current-page="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
const tableData = ref([
|
||||||
|
{
|
||||||
|
name: '张三',
|
||||||
|
phone: '12345678901',
|
||||||
|
email: '12345678901@qq.com',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const search = ref('')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-search {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
31
tsconfig.json
Normal file
31
tsconfig.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
|
||||||
|
"paths": {
|
||||||
|
//路径映射,相对于baseUrl
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
90
vite.config.ts
Normal file
90
vite.config.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
|
import Icons from 'unplugin-icons/vite'
|
||||||
|
import IconsResolver from 'unplugin-icons/resolver'
|
||||||
|
|
||||||
|
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||||
|
|
||||||
|
import { viteMockServe } from 'vite-plugin-mock'
|
||||||
|
// 引入 fs 模块
|
||||||
|
// import fs from 'fs'
|
||||||
|
// const fs = require('fs');
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ command, mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd())
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
|
||||||
|
AutoImport({
|
||||||
|
resolvers: [
|
||||||
|
ElementPlusResolver(),
|
||||||
|
IconsResolver({
|
||||||
|
prefix: 'Icon',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [
|
||||||
|
ElementPlusResolver({importStyle:"sass"}),
|
||||||
|
IconsResolver({
|
||||||
|
enabledCollections: ['ep'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
Icons({
|
||||||
|
autoInstall: true,
|
||||||
|
}),
|
||||||
|
createSvgIconsPlugin({
|
||||||
|
// Specify the icon folder to be cached
|
||||||
|
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/default'), path.resolve(process.cwd(), 'src/assets/icons/bosch')],
|
||||||
|
// Specify symbolId format
|
||||||
|
symbolId: 'icon-[dir]-[name]',
|
||||||
|
}),
|
||||||
|
viteMockServe({
|
||||||
|
localEnabled: command === 'serve',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve('./src'), // 相对路径别名配置,使用 @ 代替 src
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: { //配置全局scss
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
quietDeps: true,
|
||||||
|
logger: {
|
||||||
|
warn: () => {}
|
||||||
|
},
|
||||||
|
javascriptEnabled: true,
|
||||||
|
// additionalData: `@use "@/styles/element/index.scss" as *;`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 代理跨域
|
||||||
|
server: {
|
||||||
|
// https: {
|
||||||
|
// key: fs.readFileSync('./cert.key'), // 证书密钥路径
|
||||||
|
// cert: fs.readFileSync('./cert.crt') // 证书文件路径
|
||||||
|
// },
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
[env.VITE_APP_BASE_API]: {
|
||||||
|
target: env.VITE_SERVE,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) =>
|
||||||
|
path.replace(new RegExp(`^${env.VITE_APP_BASE_API}`), ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user