1 Commits
Author SHA1 Message Date
hezhidong bb20d0b25d 初始化 2025-03-28 09:30:07 +08:00
122 changed files with 38659 additions and 63 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
+66 -63
View File
@@ -1,92 +1,95 @@
# 西南医科大-隐患上报二期-后台管理端-前端
#💐💐💐 Vue 3.2 + vuex + Vite2 + Element-Plus 后台管理系统
![banner](./src/assets/login2.png)
### 前言 📖
## Getting started
项目基于 Vue3.2、Javascript、Vite2、vuex、Element-Plus 开发的一套后台管理模板,目前利用空余时间开发 🕒🕒🕒🕒。项目中借鉴很多优秀的后台管理系统,从 0 开始搭建项目(其中 ui 图是借鉴了【即时设计】里的优秀的设计图),整个项目还有很多地方不完善,后期会持续更新,希望大家能多提意见。
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
### ?为什么开发这个管理系统 👩‍🔬
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
- 主要是想把自己常用的组件进行一个封装 二次巩固
- 目前看了很多优秀的 Vue3 的开源后台管理系统,感觉都非常庞大(想开发一套适合自己的)
## Add your files
### 一、在线预览 🛫
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
- Linkhttp://124.221.156.158:9797/#/login
### 二、gitee 地址 🛫
- Linkhttps://gitee.com/tanzo/vue-manage-system
### 三、本地预览 🛫🛫🛫
```
cd existing_repo
git remote add origin http://gitlab.lzyyj.com/hzd/hiddenDangerBackendTwo.git
git branch -M main
git push -uf origin main
git clone 项目地址
npm install (依赖安装失败可以尝试cnpm install)
npm run dev
```
## Integrate with your tools
### 四、🔨 项目功能
- [ ] [Set up project integrations](http://gitlab.lzyyj.com/hzd/hiddenDangerBackendTwo/-/settings/integrations)
- 🚀 使用 Vue3.2 开发,单文件组件 `script setup`开发
- 🚀 采用动态路由菜单模式
- 🚀 数据模拟用到了 MockJS
- 🚀 使用 Vuex getter 模块化管理( 加入了持久化插件)
- 🚀 Axios 的二次封装 (全局错误拦截、常用请求封装、全局请求 Loading、取消重复请求……)
- 🚀 对 el-table 组件进行的二次封装
- 🚀 使用 vue-router 进行路由权限拦截(403 页面)、页面按钮权限配置、路由懒加载
- 🚀 使用 keep-alive 对整个页面进行缓存,支持多级嵌套页面(缓存路由里可配置、页面切换加入动画效果)
- 🚀 常用自定义指令的封装 直接 v-copy 即可实现复制 (复制、拖拽、、、等等)
- 🚀 vite.config.js 打包配置(静态资源合并打包/清除 log/gzip 压缩配置等)
## Collaborate with your team
### 五、项目截图 📷
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
#### 1、登录页:
## Test and Deploy
![login-light](./src/assets/login2.png)
Use the built-in continuous integration in GitLab.
#### 2、首页:
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
![home_light](./src/assets/home2.png)
***
#### 3、表格页:
# Editing this README
![table_light](./src/assets/table.png)
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
#### 4、流程图:
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
![dataScreen02](./src/assets/flow.png)
## Name
Choose a self-explaining name for your project.
### 六、文件资源目录 📚
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
```text
├─ .vscode # vscode推荐配置
├─ public # 静态资源文件(忽略打包)
├─ src
│ ├─ api # API 接口管理
│ ├─ assets # 静态资源文件
│ ├─ components # 全局组件
│ ├─ config # 全局配置项
│ ├─ instruction # 指令文件
│ ├─ layout # 框架布局
│ ├─ router # 路由管理
│ ├─ store # VUEX模块化管理
│ ├─ styles # 全局样式
│ ├─ utils # 工具库(color函数的封装)
│ ├─ views # 项目所有页面
│ ├─ App.vue # 入口页面
│ └─ main.js # 入口文件
└─ vite.config.ts # vite 配置
```
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
### 七、浏览器支持
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
> vue3.2 不支持 IE 浏览器。其他还行吧(其实我没测 🤣🧐)
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
### 八、项目后台接口 🧩🧐
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
> 项目后台接口全部采用 Mock 数据
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
### 九 最后求个关注 👀 🌟🌟🌟🙇‍♀️🙇‍♀️🙇‍♀️
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
![home_light](https://s1.328888.xyz/2022/09/18/oyxSr.gif)
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="//at.alicdn.com/t/c/font_3654542_filhj6brvds.css" />
<title>后台管理系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<!-- 百度地图引入 -->
<!-- <script type="text/javascript"
src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=Ol0d1CqPErteakupIcX4actXo6GiQI7g"></script> -->
</body>
</html>
+1562
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"name": "vue-manage-system",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "vite build",
"dev": "vite --host",
"preview": "vite preview"
},
"dependencies": {
"@braks/vue-flow": "^0.4.38",
"@element-plus/icons-vue": "^1.1.4",
"@kangc/v-md-editor": "^2.3.15",
"@wangeditor/editor": "^5.1.14",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^0.27.2",
"driver.js": "^0.9.8",
"echarts": "^5.3.3",
"element-plus": "^2.1.11",
"js-base64": "^3.7.2",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"screenfull": "^6.0.1",
"video.js": "^7.20.3",
"vue": "^3.2.25",
"vue-router": "^4.0.13",
"vue-video-player": "^6.0.0",
"vue3-print-nb": "^0.1.4",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"sass": "^1.51.0",
"vite": "^2.9.7",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "^2.9.6",
"vue-cli-plugin-style-resources-loader": "~0.1.5"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+25
View File
@@ -0,0 +1,25 @@
<template>
<router-view v-if="isRouterAlive"></router-view>
</template>
<script setup>
import { onMounted, ref, nextTick, provide } from "vue";
import { useStore } from "vuex";
// 局部组件刷新
const isRouterAlive = ref(true);
const globalStore = useStore();
const reload = () => {
isRouterAlive.value = false;
nextTick(() => {
isRouterAlive.value = true;
console.log("数据刷新成功");
});
};
provide("reload", reload);
onMounted(() => {
globalStore.dispatch("user/changeThem", "#4060c7");
});
</script>
<style scoped lang="scss"></style>
+75
View File
@@ -0,0 +1,75 @@
//src/api/user/index.js
import service from "../request.js";
export function LoginInfo(query) {
return service({
method: "POST",
url: "/permission/LoginInfo",
data: query,
});
}
export function getMenuList(query) {
return service({
method: "get",
url: "/permission/getMenuList",
data: query,
});
}
export function getUserList(query) {
return service({
method: "get",
url: "/permission/UserList",
data: query,
});
}
export function addUserList(query) {
return service({
method: "post",
url: "/permission/addUserList",
data: query,
});
}
export function listUpdate(query) {
return service({
method: "post",
url: "/permission/listUpdate",
data: query,
});
}
export function Newslist(query) {
return service({
method: "get",
url: "/permission/Newslist",
data: query,
});
}
export function orderLists(query) {
return service({
method: "get",
url: "/permission/orderLists",
data: query,
});
}
export function homeList(query) {
return service({
method: "get",
url: "/permission/homeList",
data: query,
});
}
export function noticeLists(query) {
return service({
method: "get",
url: "/permission/noticeLists",
data: query,
});
}
export function cardlists(query) {
return service({
method: "get",
url: "/permission/cardlists",
data: query,
});
}
+44
View File
@@ -0,0 +1,44 @@
import axios from "axios";
import {
ElMessage
} from "element-plus";
const service = axios.create({
timeout: 5000,
});
// Request interceptors
service.interceptors.request.use(
(config) => {
if (localStorage.getItem('token'))
config.headers.token = localStorage.getItem('token')
return config;
},
(error) => {
Promise.reject(error);
}
);
// Response interceptors
service.interceptors.response.use(
(response) => {
if (response.status !== 200) {
ElMessage({
type: "error",
message: "服务器忙,请稍后再试~",
});
return;
}
return response;
},
(error) => {
// do something
return Promise.reject(error);
}
);
export default service;
+41
View File
@@ -0,0 +1,41 @@
export const showMessage = (status) => {
let message = "";
switch (status) {
case 400:
message = "请求错误(400)";
break;
case 401:
message = "未授权,请重新登录(401)";
break;
case 403:
message = "拒绝访问(403)";
break;
case 404:
message = "请求出错(404)";
break;
case 408:
message = "请求超时(408)";
break;
case 500:
message = "服务器错误(500)";
break;
case 501:
message = "服务未实现(501)";
break;
case 502:
message = "网络错误(502)";
break;
case 503:
message = "服务不可用(503)";
break;
case 504:
message = "网络超时(504)";
break;
case 505:
message = "HTTP版本不受支持(505)";
break;
default:
message = `连接出错(${status})!`;
}
return `${message},请检查网络或联系管理员!`;
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+80
View File
@@ -0,0 +1,80 @@
<template>
<div class="videoPlay">
<video
ref="m3u8_video"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
>
<source :src="videoSrc" />
</video>
</div>
</template>
<script setup>
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const props = defineProps({
videoSrc: String,
autoPlay: Boolean,
})
const m3u8_video = ref()
let player
const initPlay = async () => {
videojs.addLanguage('zh-CN', zh)
await nextTick()
const options = {
muted: true,
controls: true,
autoplay: true,
loop: true,
language: 'zh-CN',
techOrder: ['html5'],
}
player = videojs(m3u8_video.value, options, () => {
videojs.log('播放器已经准备好了!')
if (props.autoPlay && props.videoSrc) {
player.play()
}
player.on('ended', () => {
videojs.log('播放结束了!')
})
player.on('error', () => {
videojs.log('播放器解析出错!')
})
})
}
onMounted(() => {
initPlay()
})
//直接改变路径测试
watch(
() => props.videoSrc,
() => {
player.pause()
player.src(props.videoSrc)
player.load()
if (props.videoSrc) {
player.play()
}
}
)
onBeforeUnmount(() => {
player?.dispose()
})
</script>
<style lang="scss" scoped>
.videoPlay {
width: 100%;
height: 100%;
.video-js {
height: 100%;
width: 100%;
object-fit: fill;
}
}
:deep(.vjs-tech) {
object-fit: fill;
}
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<el-dialog v-model="dialogVisible" title="添加数据">
<el-form v-model="uploadFrom">
<el-form-item label="文件上传:">
<el-upload
style="width: 80%"
drag
multiple
:limit="excelLimit"
:on-success="uploadSuccess"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>或点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="数据覆盖:">
<el-switch v-model="is_cover" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">确认</el-button>
</template>
</el-dialog>
</template>
<script setup name="filesUpload">
import { ref, defineEmits } from 'vue'
const excelLimit = ref(1)
const dialogVisible = ref(false)
const parameter = ref({})
const is_cover = ref(false)
const emit = defineEmits(['acceptParams'])
// 接收父组件参数
const acceptParams = (params) => {
parameter.value = params
emit('gatewayData', parameter.value)
dialogVisible.value = true
}
const uploadExcel = (param) => {
console.log(param)
}
const uploadSuccess = (files) => {
console.log('上传成功' + JSON.stringify(files))
}
// 接受父组件得参数
defineExpose({
acceptParams,
})
</script>
+24
View File
@@ -0,0 +1,24 @@
import draggable from "./modules/draggable";
import copy from "./modules/copy";
import debounce from "./modules/debounce";
import longPress from "./modules/longPress";
import throttle from "./modules/throttle";
const directivesList = {
draggable,
copy,
debounce,
longPress,
throttle
};
const directives = {
install: function (app) {
Object.keys(directivesList).forEach((key) => {
// 注册所有自定义指令
app.directive(key, directivesList[key]);
});
},
};
export default directives;
+58
View File
@@ -0,0 +1,58 @@
/*
需求:实现一个复制指定。复制指定内容至剪贴板
1.动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
2.将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
3.选中值 textarea 并复制
4.将 body 中插入的 textarea 移除
5.在第一次调用时绑定事件,在解绑时移除事件
*/
import { ElMessage } from "element-plus";
export const copy = {
mounted: function (el, { value }) {
console.log(arguments);
el.$value = value;
el.onclick = () => {
if (!el.$value) {
// 值为空的时候,给出提示。可根据项目UI仔细设计
ElMessage({
type: "error",
message: "无复制内容",
});
return;
}
// 动态创建 textarea 标签
const textarea = document.createElement("input");
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = "readonly";
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value;
// 将 textarea 插入到 body 中
document.body.appendChild(textarea);
// 选中值并复制
textarea.select();
const result = document.execCommand("Copy");
if (result) {
ElMessage({
type: "success",
message: "复制成功",
});
}
document.body.removeChild(textarea);
};
// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener("click", el.handler);
},
// 当传进来的值更新的时候触发
beforeUpdate(el, { value }) {
el.$value = value;
},
// 指令与元素解绑的时候,移除事件绑定
unmounted(el) {
el.removeEventListener("click", el.handler);
},
};
export default copy;
+28
View File
@@ -0,0 +1,28 @@
/**
* v-debounce
* 按钮防抖指令,可自行扩展至input
* 接收参数:function类型
*/
const debounce = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let timer = null;
el.__handleClick__ = function () {
// if (timer) clearInterval(timer);
//
timer && clearTimeout(timer)
timer = setTimeout(() => {
binding.value();
}, 3000);
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el) {
el.removeEventListener("click", el.__handleClick__);
},
};
export default debounce;
+47
View File
@@ -0,0 +1,47 @@
/*
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
思路:
1、设置需要拖拽的元素为absolute,其父元素为relative。
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
4、鼠标松开(onmouseup)时完成一次拖拽
使用:在 Dom 上加上 v-draggable 即可
<div class="dialog-model" v-draggable></div>
*/
const draggable = {
mounted: function (el) {
console.log(el)
el.style.cursor = "move";
el.style.position = "absolute";
el.onmousedown = function (e) {
let disX = e.pageX - el.offsetLeft;
let disY = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
let x = e.pageX - disX;
let y = e.pageY - disY;
console.log(el.parentNode.offsetWidth)
let maxX = el.parentNode.offsetWidth - el.offsetWidth;
let maxY = el.parentNode.offsetHeight - el.offsetHeight;
if (x < 0) {
x = 0;
} else if (x > maxX) {
x = maxX;
}
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + "px";
el.style.top = y + "px";
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
}
};
export default draggable;
+44
View File
@@ -0,0 +1,44 @@
const longPress = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let pressTimer = null;
let start = (e) => {
if (e.type === "click" && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 执行函数
handler();
}, 2000);
}
};
// 取消计时器
let cancel = () => {
// 检查计时器是否有值
if (pressTimer !== null) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
// 运行函数
const handler = (e) => {
// 执行传递给指令的方法
binding.value(e);
console.log(e);
};
// 添加事件监听器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
el.addEventListener("click", start);
// 取消计时器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
},
};
export default longPress;
+30
View File
@@ -0,0 +1,30 @@
/**
* 一段时间内控制请求的频率
* 上一次时间触发的时间和这次触发的时间相减 传入节流的时间
*
*
* 应用场景:scroll加载的时候 和input框输入的情况
*/
const throttle = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let lastTime = 0
let newtimer = +new Date(); //拿到当前的时间
el.__handleClick__ = function () {
if (newtimer - lastTime > 3000) {
binding.value()
lastTime = newtimer
}
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el) {
el.removeEventListener("click", el.__handleClick__);
},
}
export default throttle
+79
View File
@@ -0,0 +1,79 @@
<template>
<div class="tags">
<el-tag
ref="tagRef"
v-for="tag in tags"
:key="tag.title"
class="mR10 tabs"
:closable="tag.close"
:effect="tag.checked ? 'dark' : 'plain'"
@click="tagChange(tag)"
@close="delectTag(tag, index)"
>
<el-icon class="tabs-icon">
<component :is="tag.icon"></component>
</el-icon>
{{ tag.title }}
</el-tag>
</div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import store from '../../store/index.js'
import { useStore } from 'vuex'
const tabsMenuValue = ref('')
const nameScroll = ref(false)
const tagRef = ref()
const route = useRoute()
const router = useRouter()
const globalStore = useStore() // 该方法用于返回store 实例
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue
watch(
() => route.path,
() => {
let params = {
title: route.meta.name,
url: route.path,
icon: 'Menu',
close: true,
checked: true,
}
globalStore.dispatch('tabs/addTabs', params)
},
{
immediate: true,
}
)
const tags = store.getters.tabsMenuList
const tagChange = (item) => {
item.checked = true
router.push(item.url)
}
const delectTag = (item, index) => {
globalStore.dispatch('tabs/delectTag', item)
}
onMounted(() => {
nextTick(() => {})
})
</script>
<style lang="scss" scoped>
.tags {
width: 95%;
overflow: hidden;
height: 100%;
display: flex;
flex-flow: row;
overflow: scroll;
white-space: nowrap;
&::-webkit-scrollbar {
display: none;
}
.tabs {
height: 30px;
}
}
</style>
View File
@@ -0,0 +1,64 @@
<template>
<el-icon class="icon-style" id="collapseIcon" @click="changeShrinkage">
<component :is="!isCollapse ? 'Fold' : 'Expand'"></component>
</el-icon>
<el-breadcrumb separator="/" id="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span class="no-redirect" v-if="index === breadcrumbList.length - 1" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
<span class="redirect" v-else @click="handleRedirect(item.path)" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { watch, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import store from "../../../store";
import { useStore } from "vuex";
const route = useRoute();
const router = useRouter();
const breadcrumbList = ref([]);
const isCollapse = ref(false);
const globalStore = useStore();
const themeConfig = store.getters.themeConfig;
const initBreadcrumbList = () => {
breadcrumbList.value = route.matched;
console.log(route.matched);
};
const handleRedirect = (path) => {
router.push(path);
};
const changeShrinkage = () => {
isCollapse.value = !store.getters.isCollapse;
globalStore.dispatch("user/changeIsCollapse", isCollapse.value);
};
watch(
route,
() => {
initBreadcrumbList();
},
{ deep: true, immediate: true }
);
</script>
<style lang="scss" scoped>
.no-redirect {
// color: #97a8be;
cursor: text;
}
.redirect {
font-weight: 600;
cursor: pointer;
&:hover {
color: #97a8be;
}
}
.icon-style {
font-size: 20px;
cursor: pointer;
margin-right: 20px;
}
</style>
@@ -0,0 +1,25 @@
export const steps = () => [{
element: "#guide",
popover: {
title: "菜单栏",
description: "Body of the popover",
position: "left",
},
},
{
element: "#breadcrumb",
popover: {
title: "标签页",
description: "",
position: "bottom",
},
},
{
element: "#screenFul",
popover: {
title: "全屏",
description: "Body of the popover",
position: "bottom",
},
},
];
@@ -0,0 +1,33 @@
<template>
<el-icon @click="handleDriver"><Position /></el-icon>
</template>
<script setup>
import Driver from 'driver.js'
import 'driver.js/dist/driver.min.css'
import { onMounted, ref } from 'vue'
import { steps } from './index.js'
let driver = ref('')
onMounted(() => {
driver.value = new Driver({
className: 'scoped-class', //包裹driver.js弹窗的类名 className to wrap driver.js popover
animate: true, // 高亮元素改变时是否显示动画 Animate while changing highlighted element
opacity: 0.75, //背景透明度(0 表示只有弹窗并且没有遮罩) Background opacity (0 means only popovers and without overlay)
padding: 10, // 元素与边缘的距离 Distance of element from around the edges
allowClose: true, // 是否允许点击遮罩时关闭 Whether clicking on overlay should close or not
overlayClickNext: false, //是否允许点击遮罩时移到到下一步 Should it move to next step on overlay click
doneBtnText: '完成', // 最终按钮上的文本 Text on the final button
closeBtnText: '跳过', // 当前步骤关闭按钮上的文本 Text on the close button for this step
nextBtnText: '下一步', //当前步骤下一步按钮上的文本 Next button text for this step
prevBtnText: '上一步',
})
})
const handleDriver = () => {
driver.value.defineSteps(steps)
driver.value.start()
}
</script>
<style lang="scss" scoped></style>
+66
View File
@@ -0,0 +1,66 @@
<template>
<el-icon class="icon-style" @click="dialogVisible = true"><Search /></el-icon>
<el-dialog v-model="dialogVisible" :show-close="false">
<template #header>
<el-input
v-model="searchValue"
placeholder="搜索"
class="input-with-select"
>
<template #prepend>
<el-button :icon="Search" />
</template>
</el-input>
</template>
<div class="centent col-center">暂无搜索结果🦁🦁🦁🦁</div>
<template #footer>
<div class="footer-tip flx-row">
<span></span> 确认 <span class="ml10"></span>切换
<span class="ml10" style="font-size: 10px">ESC</span>关闭
</div>
</template>
</el-dialog>
</template>
<script setup>
import { Search, Top, Bottom } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
const dialogVisible = ref(false)
const searchValue = ref('')
onMounted(() => {
nextTick(() => {
document.addEventListener('keyup', function (e) {
if (e.keyCode == 27) {
dialogVisible.value = false
}
})
})
})
</script>
<style lang="scss" scoped>
.input-with-select {
height: 40px;
}
.footer-tip {
height: 20px;
padding: 0;
font-size: 12px;
span {
display: flex;
width: 20px;
height: 18px;
padding-bottom: 2px;
margin-right: 0.4em;
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
border-radius: 2px;
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px #1e235a66;
align-items: center;
justify-content: center;
}
}
</style>
+62
View File
@@ -0,0 +1,62 @@
<template>
<el-dropdown placement="bottom">
<div class="flx-center">
<div class="avatar">
<img src="../../../assets/images/avart.jpg" alt="avatar" />
</div>
<span class="username" :style="{ color: themeConfig.footColor }">BIG CUTE</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item>个人中心</el-dropdown-item> -->
<el-dropdown-item @click="gotoLogin">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { ArrowDown } from "@element-plus/icons-vue";
import { nextTick, onMounted, ref } from "vue";
import store from "../../../store/index.js";
import { useRouter } from "vue-router";
const router = useRouter();
const themeConfig = store.getters.themeConfig;
const handleClick = () => {
// eslint-disable-next-line no-alert
alert("button click");
};
const gotoLogin = () => {
sessionStorage.removeItem("token");
router.push("/login");
};
</script>
<style lang="scss" scoped>
.el-dropdown {
cursor: pointer;
.el-tooltip__trigger {
display: flex;
flex-flow: row;
align-items: center;
}
}
.username {
font-size: 15px;
margin: 0 0 0 10px;
}
.avatar {
width: 35px;
height: 35px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
margin-left: 20px;
img {
width: 100%;
height: 100%;
}
}
</style>
@@ -0,0 +1,33 @@
<template>
<el-icon id="fullscreen" class="icon-style" @click="handleFullScreen">
<component :is="icon ? 'Rank' : 'FullScreen'"></component>
</el-icon>
</template>
<script setup>
import screenfull from 'screenfull'
import { ref, onMounted, onBeforeMount } from 'vue'
import { ElMessage } from 'element-plus'
let icon = ref(screenfull.isFullscreen)
const handleFullScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
const changeIcon = () => {
icon.value = screenfull.isFullscreen
}
onMounted(() => {
screenfull.on('change', changeIcon)
})
onBeforeMount(() => {
screenfull.off('change')
})
</script>
<style scoped></style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<el-popover :width="400" placement="bottom" trigger="click">
<!-- <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> -->
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="通知" name="first">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type">通知</span> {{ item.text }}
</div>
</div>
</el-tab-pane>
<el-tab-pane label="公告" name="second">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type">公告</span> {{ item.text }}
</div>
</div>
</el-tab-pane>
<el-tab-pane label="系统消息" name="third">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type" style="color: rgb(47, 96, 194); background-color: #e8f3ff"
>系统消息</span
>
{{ item.text }}
</div>
</div>
</el-tab-pane>
</el-tabs>
<template #reference>
<el-icon class="icon-style" @click="visible = !visible"><bell /></el-icon>
</template>
</el-popover>
</template>
<script setup name="messages">
import { Bell } from "@element-plus/icons-vue";
import { nextTick, onMounted, ref } from "vue";
import { noticeLists } from "../../../api/modules/index.js";
const visible = ref(false);
const activeName = ref("first");
const notList = ref("first");
onMounted(() => {
nextTick(() => {
noticeLists().then((res) => {
notList.value = res.data.data;
});
});
});
</script>
<style lang="scss" scoped>
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 11px;
}
.notice-lists {
height: 260px;
overflow: scroll;
.item {
font-size: 14px;
padding: 10px;
color: #666;
.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
}
}
}
</style>
+192
View File
@@ -0,0 +1,192 @@
<template>
<el-icon @click="openDrawer" class="icon-style"><Setting /></el-icon>
<el-drawer v-model="drawerVisible" title="布局设置" size="300px">
<el-divider>主题颜色</el-divider>
<div class="flx-row">
<div
class="theme-item"
v-for="item in colorList"
:key="item"
:style="{ backgroundColor: item }"
@click="changePrimary(item)"
>
<el-icon v-if="item == themeConfig.primary" class="icon"
><Select
/></el-icon>
</div>
</div>
<el-divider>导航栏颜色</el-divider>
<div class="flx-row">
<div
class="theme-item"
v-for="item in tabColorList"
:key="item"
:style="{ backgroundColor: item }"
@click="changeTabCole(item)"
>
<el-icon v-if="item == themeConfig.tabColor" class="icon"
><Select
/></el-icon>
</div>
</div>
<!-- <el-switch v-model="value2" class="mt-2" style="margin-left: 24px" inline-prompt :active-icon="Check" :inactive-icon="Close" /> -->
<div class="flx-row">
<div class="flx-tit">侧边栏模式</div>
<el-switch
v-model="value2"
@change="changeGreyOrWeak($event)"
class="mt-2"
style="margin-left: 24px"
inline-prompt
:active-icon="Sunny"
:inactive-icon="Moon"
/>
</div>
<div class="flx-row">
<div class="flx-tit">标签栏是否显示</div>
<el-switch
v-model="istags"
@change="changeTags($event)"
class="mt-2"
style="margin-left: 24px"
/>
</div>
<!-- <div class="theme-item">
<el-color-picker
v-model="themeConfig.primary"
:predefine="colorList"
@change="changePrimary"
>
</el-color-picker>
</div> -->
<!-- <div class="theme-item">
<span>暗黑模式</span>
<SwitchDark></SwitchDark>
</div>
<div class="theme-item">
<span>灰色模式</span>
<el-switch
v-model="themeConfig.isGrey"
@change="changeGreyOrWeak($event, 'grey')"
/>
</div>
<div class="theme-item">
<span>色弱模式</span>
<el-switch
v-model="themeConfig.isWeak"
@change="changeGreyOrWeak($event, 'weak')"
/>
</div>
<br />
<el-divider class="divider" content-position="center">
<el-icon><Setting /></el-icon>
界面设置
</el-divider>
<div class="theme-item">
<span>折叠菜单</span>
<el-switch v-model="isCollapse" />
</div>
<div class="theme-item">
<span>面包屑导航</span>
<el-switch v-model="themeConfig.breadcrumb" />
</div>
<div class="theme-item">
<span>标签栏</span>
<el-switch v-model="themeConfig.tabs" />
</div>
<div class="theme-item">
<span>页脚</span>
<el-switch v-model="themeConfig.footer" />
</div> -->
</el-drawer>
</template>
<script setup>
import { ref, computed, useAttrs } from 'vue'
import { Sunny, Moon } from '@element-plus/icons-vue'
import store from '../../../store/index.js'
import { mix } from '../../../utils/color.js'
import { useStore } from 'vuex'
const value2 = ref(true)
const istags = computed(() => {
return store.getters.themeConfig.istags
})
// 预定义主题颜色
const colorList = [
'#4060c7',
'#409EFF',
'#009688',
'#27ae60',
'#e74c3c',
'#fd726d',
'#f39c12',
'#9b59b6',
]
// 预导航栏颜色
const tabColorList = [
'#FFFFFF',
'#333333',
'#009688',
'#27ae60',
'#e74c3c',
'#fd726d',
'#f39c12',
'#9b59b6',
]
// 主题初始化
const globalStore = useStore()
const themeConfig = computed(() => {
return store.getters.themeConfig
})
const changePrimary = (val) => {
globalStore.dispatch('user/changeThem', val)
}
const changeTabCole = (val) => {
globalStore.dispatch('user/changeTabColor', val)
}
const drawerVisible = ref(false)
const openDrawer = () => {
drawerVisible.value = true
}
const changeGreyOrWeak = (e) => {
globalStore.dispatch('user/changeMenuColor', e)
}
const changeTags = (e) => {
globalStore.dispatch('user/changeTags', e)
}
</script>
<style scoped lang="scss">
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 11px;
}
.flx-row {
flex-wrap: wrap;
}
.flx-tit {
color: #303133;
flex: 1;
font-size: 14px;
}
.theme-item {
margin: 16px 5px;
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #ddd;
border-radius: 2px;
position: relative;
.icon {
color: #fff;
position: absolute;
top: 3px;
right: 3px;
font-size: 14px;
}
}
</style>
+89
View File
@@ -0,0 +1,89 @@
<template>
<div
class="header"
:style="{ backgroundColor: themeConfig.tabColor, color: themeConfig.footColor }"
>
<div class="header-lf flx-center">
<Breadcrumb></Breadcrumb>
</div>
<div class="header-ri flx-center">
<!-- <Search></Search> -->
<el-icon class="icon-style" @click="refresh"><Refresh /></el-icon>
<!-- <message></message> -->
<!-- <Driver></Driver> -->
<fullScreen></fullScreen>
<avatar></avatar>
<setting></setting>
</div>
</div>
</template>
<script setup name="header">
import { inject, ref } from "vue";
import store from "../../store/index.js";
// import { Setting, Search } from '@element-plus/icons-vue'
import avatar from "./components/avatar.vue";
import setting from "./components/setting.vue";
import Search from "./components/Search.vue";
import fullScreen from "./components/fullScreen.vue";
import message from "./components/message.vue";
import Breadcrumb from "./components/Breadcrumb.vue";
// import Driver from "./components/Driver/index.vue";
// 局部数据刷新
const refresh = inject("reload");
const themeConfig = store.getters.themeConfig;
</script>
<style scoped lang="scss">
.header {
border-bottom: 1px solid #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
height: 48px;
line-height: 48px;
.header-lf {
.collapse-icon {
font-size: 22px;
margin-right: 20px;
cursor: pointer;
}
}
.header-ri {
// margin-left:10px;
.message-box {
height: 100%;
position: relative;
.dot {
position: absolute;
top: -5px;
right: 10px;
background-color: #d54f50;
border-radius: 50%;
width: 8px;
height: 8px;
}
.bell-icon {
font-size: 22px;
margin-right: 20px;
cursor: pointer;
}
}
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 0 0 11px;
}
}
}
</style>
+65
View File
@@ -0,0 +1,65 @@
.el-container {
width: 100%;
height: 100vh;
overflow: hidden;
.el-aside {
width: auto;
overflow: inherit;
}
.el-header,
.el-footer {
height: auto;
padding: 0;
}
.main-tabs {
width: 100%;
margin-bottom: 10px;
background-color: #fff;
border: 0;
box-shadow: none;
overflow: hidden;
height: 30px;
}
.el-main {
background: #f0f2f5;
padding: 10px 13px;
box-sizing: border-box;
// 防止切换出现横向滚动条
overflow-x: hidden;
.main-box {
width: 100%;
height: 100%;
// background-color: #f5f5f5;
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
// border-radius: 4px;
// padding: 20px;
box-sizing: border-box;
overflow: auto;
overflow-x: hidden !important;
&::-webkit-scrollbar {
background-color: white;
}
}
}
}
.shake {
animation: shake 0.5s linear;
}
@keyframes shake {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
+61
View File
@@ -0,0 +1,61 @@
<template>
<el-container class="container">
<el-aside :style="{ backgroundColor: themeConfig.backgroundColor }"
><Menu></Menu
></el-aside>
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-main>
<div class="main-tabs flx-row" v-if="themeConfig.istags">
<tabs></tabs>
</div>
<div class="main-box">
<router-view :class="{ shake: disabled }"></router-view>
</div>
</el-main>
</el-container>
</el-container>
</template>
<script setup name="layout">
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
import { ref, watch } from 'vue'
import store from '../store/index.js'
import Header from './header/index.vue'
import Menu from './menu/index.vue'
import tabs from './Tabs/index.vue'
const themeConfig = store.getters.themeConfig
console.log(themeConfig.backgroundColor)
let router = useRouter()
const disabled = ref(false)
// onMounted(() => {
// disabled.value = true
// setTimeout(() => {
// disabled.value = false
// }, 1500)
// })
watch(
() => router.currentRoute.value.path,
(newValue, oldValue) => {
console.log('watch', newValue)
console.log('watch', oldValue)
if (newValue == oldValue) {
} else {
disabled.value = true
setTimeout(() => {
disabled.value = false
}, 1000)
}
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
@import './index.scss';
</style>
@@ -0,0 +1,48 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu
v-if="subItem.children && subItem.children.length > 0"
:index="subItem.path"
>
<template #title>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<span>{{ subItem.meta.title }}</span>
</template>
<Menu-items :menuList="subItem.children"></Menu-items>
<!--<template v-for="child in subItem.children" :key="child.path">
<template v-if="child.children && child.children.length > 0">
<Menu-items :menuList="child.children"></Menu-items>
</template>
<el-menu-item v-else :index="child.path">
<el-icon>
<component :is="child.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ child.meta.title }}</span>
</template>
</el-menu-item>
</template> -->
</el-sub-menu>
<el-menu-item v-else :index="subItem.path">
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
<script setup>
defineProps(["menuList"]);
</script>
<style scoped>
.icons {
width: 24px;
height: 18px;
}
</style>
+32
View File
@@ -0,0 +1,32 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-menu-item v-if="!subItem.children" :index="subItem.url" :route="subItem.url">
<el-icon>
<component :is="subItem.icon"></component>
</el-icon>
<span>{{ subItem.title }}</span>
</el-menu-item>
<!--有多级菜单-->
<el-sub-menu v-if="subItem.children" :key="subItem.url" :index="subItem.url">
<template #title>
<el-icon>
<component :is="subItem.icon"></component>
</el-icon>
<span> {{ subItem.title }}</span>
</template>
<!--递归组件把遍历的值传回子组件完成递归调用-->
<menu-items :menuList="subItem.children"></menu-items>
</el-sub-menu>
</template>
</template>
<script setup>
defineProps(["menuList"]);
</script>
<style scoped>
.icons {
width: 24px;
height: 18px;
}
</style>
+118
View File
@@ -0,0 +1,118 @@
.menu {
transition: all 0.3s ease;
background: #20222a;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
.logo {
transition: width 0.3s ease;
height: 55px;
box-sizing: border-box;
border-bottom: 2px solid #1d1e26;
box-shadow: 2px 0 6px rgb(0 21 41 / 35%);
span {
font-size: 22px;
font-weight: bold;
color: #dadada;
white-space: nowrap;
}
img {
width: 30px;
object-fit: contain;
margin-right: 8px;
}
}
.el-scrollbar {
height: calc(100% - 55px);
.el-menu {
// flex: 1;
overflow: auto;
overflow-x: hidden;
border: none !important;
width: 100% !important;
&::-webkit-scrollbar {
background-color: #20222a;
}
&::-webkit-scrollbar-thumb {
background-color: #41444b;
}
.el-menu-item {
width: 200px;
&.is-active {
background-color: #060708 !important;
}
&.is-active::before {
content: "";
top: 0;
left: 0;
bottom: 0;
width: 4px;
background: $bg-color;
position: absolute;
}
}
.href {
display: inline-block;
text-decoration: none;
color: #bdbdc0;
width: 100%;
height: 100%;
}
}
}
}
// .el-menu--popup {
// .el-menu-item {
// background-color: #20222a;
// width: 200px;
// i {
// margin-right: 5px;
// }
// i,
// span {
// color: hsla(0, 0%, 100%, 0.7);
// }
// //
// &:hover {
// i,
// span {
// color: #fff !important;
// }
// }
// &.is-active {
// background-color: #060708 !important;
// &:before {
// content: "";
// top: 0;
// left: 0;
// bottom: 0;
// width: 4px;
// background: $bg-color;
// position: absolute;
// }
// i,
// span {
// color: #fff !important;
// }
// }
// }
// }
+82
View File
@@ -0,0 +1,82 @@
<template>
<div
id="guide"
class="menu"
:style="{
width: store.getters.isCollapse == true ? '' : '200px',
height: '100%',
color: themeConfig.textColor,
}"
>
<div class="logo" v-if="!store.getters.isCollapse">
<img src="../../assets/logo.png" alt="" style="margin-right: 5px" /> BIG CUTE
</div>
<div class="logo" v-else>
<img src="../../assets/logo.png" alt="" />
</div>
<el-scrollbar style="height: 100%">
<el-menu
:default-active="activeMenu"
:router="true"
:collapse="store.getters.isCollapse"
:collapse-transition="false"
:unique-opened="true"
:background-color="themeConfig.backgroundColor"
:text-color="themeConfig.textColor"
:active-text-color="themeConfig.primary"
>
<menuItems :menuList="menuList"></menuItems>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { getMenuList } from "../../api/modules/index.js";
import store from "../../store/index.js";
import menuItems from "./components/menuItems.vue";
// 引入本地路由表渲染导航栏
import menuTable from "./menuTable.js";
import { useRouter, useRoute } from "vue-router";
const route = useRoute();
const activeMenu = computed(() => {
return route.path;
});
const menuList = ref([]);
const handleOpen = (key, keyPath) => {
console.log(key, keyPath);
};
const handleClose = (key, keyPath) => {
console.log(key, keyPath);
};
const themeConfig = store.getters.themeConfig;
// 菜单表存在后端的操作方式
onMounted(() => {
getMenuList().then((res) => {
menuList.value = res.data.data.menuList;
console.log(menuItems.value);
});
// 获取本地菜单表渲染
// menuList.value = menuTable;
});
</script>
<style scoped lang="scss">
.menu {
// width: 200px;
.logo {
height: 48px;
line-height: 48px;
padding: 0 20px;
list-style: none;
cursor: pointer;
position: relative;
img {
width: 25px;
vertical-align: middle;
}
}
}
</style>
+109
View File
@@ -0,0 +1,109 @@
// 菜单列表改了路由要记得改菜单相互匹配
const menuTable = [
{
title: "首页",
url: "/home",
icon: "HomeFilled",
},
{
title: "页面管理",
url: "/page",
icon: "Checked",
children: [
{
title: "表单页",
url: "/form",
icon: "Checked",
children: [
{
title: "基础表单",
url: "/baseForm",
icon: "Menu",
},
{
url: "/stepFrom",
title: "步骤表单",
icon: "Menu",
},
{
url: "/advancedForm",
title: "高级表单",
icon: "Menu",
},
],
},
{
title: "/system",
title: "表格管理",
icon: "Grid",
children: [
{
url: "/Department",
title: "基础表格",
icon: "Menu",
},
{
url: "/UserList",
title: "内嵌表格",
icon: "Menu",
},
{
url: "/RoleList",
title: "滑动加载",
icon: "Menu",
},
{
url: "/MenuList",
title: "可编辑Table",
icon: "Menu",
},
{
url: "/importExcel",
title: "导入Excel",
icon: "Menu",
},
],
},
{
url: "/goods",
title: "列表页",
icon: "List",
children: [
{
url: "/goodCategory",
title: "基础列表",
icon: "Menu",
},
{
url: "/cardList",
title: "卡片列表",
icon: "Menu",
},
{
url: "/searchList",
title: "搜索列表",
icon: "Menu",
},
],
},
{
url: "/ErrorMessage",
title: "异常页面",
icon: "WarningFilled",
children: [
{
url: "/404",
title: "404",
icon: "Menu",
},
{
url: "/500",
title: "500",
icon: "Menu",
},
],
},
],
},
];
export default menuTable;
+34
View File
@@ -0,0 +1,34 @@
import {
createApp
} from "vue";
import router from "./router/index";
import store from "../src/store/index";
import './styles/common.scss'
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";
// 引入注册自定义指令
import directives from "./instruction/index.js";
import "element-plus/dist/index.css";
import App from "./App.vue";
import "./mock/index.js";
import * as ELIcons from "@element-plus/icons-vue";
import VMdEditor from "@kangc/v-md-editor";
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js";
import "@kangc/v-md-editor/lib/style/base-editor.css";
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
VMdEditor.use(vuepressTheme);
const app = createApp(App);
// 小图标引用
for (const iconName in ELIcons) {
// 注册组件
app.component(iconName, ELIcons[iconName]);
}
app.use(router).use(store).use(directives).use(VMdEditor).use(ElementPlus, {
locale: zhCn,
});
app.mount("#app");
+25
View File
@@ -0,0 +1,25 @@
import Mock from "mockjs";
import {
UserList,
addUserList,
listUpdate,
LoginInfo,
getMenuList,
Newslist,
orderLists,
homeList,
noticeLists,
cardlists
} from "./permission.js";
Mock.mock("/permission/LoginInfo", "post", LoginInfo);
Mock.mock("/permission/getMenuList", "get", getMenuList);
Mock.mock("/permission/UserList", "get", UserList);
Mock.mock("/permission/addUserList", "post", addUserList);
Mock.mock("/permission/listUpdate", "post", listUpdate);
Mock.mock("/permission/Newslist", "get", Newslist);
Mock.mock("/permission/orderLists", "get", orderLists);
Mock.mock("/permission/homeList", "get", homeList);
Mock.mock("/permission/noticeLists", "get", noticeLists);
Mock.mock("/permission/cardlists", "get", cardlists);
+494
View File
@@ -0,0 +1,494 @@
// 引入mockjs
import Mock from "mockjs";
import {
options
} from "../views/home/options";
const Random = Mock.Random;
let menuList = [{
title: "首页",
url: "/home",
icon: "HomeFilled",
},
{
title: "页面管理",
url: "/page",
icon: "Checked",
children: [{
title: "表单页",
url: "/form",
icon: "Checked",
children: [{
title: "基础表单",
url: "/baseForm",
icon: "Menu",
},
{
url: "/stepFrom",
title: "步骤表单",
icon: "Menu",
},
{
url: "/advancedForm",
title: "高级表单",
icon: "Menu",
},
],
},
{
title: "/system",
title: "表格管理",
icon: "Grid",
children: [{
url: "/Department",
title: "基础表格",
icon: "Menu",
},
{
url: "/UserList",
title: "内嵌表格",
icon: "Menu",
},
{
url: "/RoleList",
title: "滑动加载",
icon: "Menu",
},
{
url: "/MenuList",
title: "可编辑Table",
icon: "Menu",
},
{
url: "/importExcel",
title: "导入Excel",
icon: "Menu",
},
],
},
{
url: "/goods",
title: "列表页",
icon: "List",
children: [{
url: "/goodCategory",
title: "基础列表",
icon: "Menu",
},
{
url: "/cardList",
title: "卡片列表",
icon: "Menu",
},
{
url: "/searchList",
title: "搜索列表",
icon: "Menu",
},
],
}, {
url: "/ErrorMessage",
title: "异常页面",
icon: "WarningFilled",
children: [{
url: "/404",
title: "404",
icon: "Menu",
},
{
url: "/500",
title: "500",
icon: "Menu",
},
],
},
]
},
{
title: "图形图表",
url: "/echarts",
icon: "Histogram",
children: [{
title: "地图",
url: "/echarts/map",
icon: "Menu",
children: [{
url: "/baidumap",
title: "百度地图",
icon: "Menu",
},
// {
// url: "/gaodemap",
// title: "高德地图",
// icon: "Menu",
// },
],
},
{
title: "雷达图",
url: "/radar",
icon: "Menu",
},
{
title: "柱状图",
url: "/histogram",
icon: "Menu",
},
{
title: "折线图",
url: "/line",
icon: "Menu",
},
],
},
{
url: "/able",
title: "功能",
icon: "HelpFilled",
children: [{
url: "/watermark",
title: "水印",
icon: "Menu",
},
{
url: "/countTo",
title: "数字动画",
icon: "Menu",
},
{
url: "/batchImport",
title: "图片上传",
icon: "Menu",
},
{
url: "/fileImport",
title: "文件上传",
icon: "Menu",
},
{
url: "markdown",
icon: "Platform",
title: "编辑器",
children: [{
url: "/wangEditor",
title: "富文本编辑器",
icon: "Menu",
},
{
url: "/markdown",
title: "markdown",
icon: "Menu",
},
],
},
{
url: "/strength",
title: "密码强度",
icon: "Menu",
},
{
url: "/validation",
title: "验证组件",
icon: "Menu",
},
{
url: "/guide",
title: "引导页",
icon: "Menu",
},
{
url: "/embedded",
title: "内嵌页",
icon: "Menu",
},
],
},
{
url: "/directives",
title: "自定义指令",
icon: "Stamp",
children: [{
url: "/copy",
title: "复制",
icon: "Menu",
},
{
url: "/Drag",
title: "拖拽",
icon: "Menu",
},
{
url: "/debounceDirect",
title: "防抖指令",
icon: "Menu",
},
{
url: "/throttle",
title: "节流指令",
icon: "Menu",
},
{
url: "/longPress",
title: "长按指令",
icon: "Menu",
},
],
},
{
url: "/flow",
title: "图形编辑器",
icon: "BrushFilled",
//
children: [{
url: "/flowCat",
title: "流程图",
icon: "Menu",
}, ],
},
{
url: "/video",
title: "视频播放器",
icon: "VideoCameraFilled",
//
children: [{
url: "/video",
title: "视频播放器",
icon: "Menu",
}, ],
},
{
url: "/DataReport",
title: "数据统计",
icon: "TrendCharts",
children: [{
url: "/demo1",
title: "项目一",
icon: "Menu",
}, ],
},
// {
// url: "/material",
// title: "素材中心",
// icon: "PictureFilled",
// children: [{
// url: "/materialIndex",
// title: "素材管理",
// icon: "Menu",
// }, ],
// },
{
url: "/user",
title: "个人中心",
icon: "Avatar",
children: [{
url: "/user",
title: "关于我",
icon: "Menu",
}, ],
},
];
export const LoginInfo = (options) => {
console.log(options, "接收post参数");
const {
username,
password
} = JSON.parse(options.body);
if (username == "admin" && password != "123456") {
return {
code: "-200",
data: {
message: "用户不存在",
},
};
} else {
return {
code: "200",
data: {
user_id: Random.id(),
name: Random.cname(),
token: Random.guid(),
image: "https://img2.baidu.com/it/u=2859542338,3761174075&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1660064400&t=6fe6057370cbe369654ff2e132d02a37",
},
};
}
};
export const getMenuList = (options) => {
const obj = JSON.parse(options.body);
return {
code: 200,
data: {
menuList: menuList,
},
};
};
// 用户列表
let userList = [];
for (let index = 0; index < 50; index++) {
let obj = {
id: Random.id(),
username: Random.cname(),
email: Random.email(),
date: Random.date(),
address: Random.city(true),
content: Random.csentence(),
};
userList.push(obj);
}
const param2Obj = (url) => {
let obj = JSON.parse(url);
let page = obj.page;
console.log(page);
return page;
};
export const UserList = (options) => {
const currentPage = param2Obj(options.body);
let cameraData = userList.filter((item, index) => {
return index >= (currentPage - 1) * 10 && index < currentPage * 10;
});
return {
code: 200,
data: {
total: userList.length,
userList: cameraData,
},
};
};
export const addUserList = (options) => {
console.log("传过来的数据" + JSON.parse(options.body));
let obj = JSON.parse(options.body);
obj.id = Random.id();
userList.unshift(obj); // 将前台返回来的数据,拼接到数组中。
return {
data: userList,
id: obj.id,
};
};
// 数据的修改操作
export const listUpdate = (options) => {
let obj = JSON.parse(options.body);
userList = userList.map((val) => {
return val.id == obj.id ? obj : val;
});
return {
data: userList,
};
};
export const Newslist = (options) => {
let obj = JSON.parse(options.body);
let newList = [];
for (let i = 0; i < 10; i++) {
let item = {
title: Random.csentence(5, 8), // Random.csentence( min, max )
notifyPic: Random.dataImage("300x250", "mock的图片"), // Random.dataImage( size, text ) 生成一段随机的 Base64 图片编码
notifyType: Random.integer(1, 3), //随机生成1-3的Integer
isTop: Random.integer(1, 2), //随机生成1-2的Integer
createUser: Random.cname(), // Random.cname() 随机生成一个常见的中文姓名
email: Random.email(),
number: '$' + Random.integer(100, 5000) + '.00',
createTime: Random.date() + " " + Random.time(),
pay_state: '100' + Random.integer(1, 3),
};
newList.push(item);
}
return {
data: newList,
};
};
export const orderLists = (options) => {
let obj = JSON.parse(options.body);
let orderList = [];
for (let i = 0; i < 60; i++) {
let item = {
goodsId: i,
code: Random.guid(),
title: Random.ctitle(4, 5),
commodity: "10010" + i,
goodsname: Random.cword(2, 6), // Random.csentence( min, max )
brand: Random.cword(2, 4),
sku: "1ml", // Random.dataImage( size, text ) 生成一段随机的 Base64 图片编码
inventory: Random.integer(10, 100),
number: Random.integer(100, 5000), //100到5000的随机整数
costPrice: Random.integer(10, 20),
amount: Random.integer(100, 200), //100到5000的随机整数
itemEdit: false,
ItemData: [{
address: Random.city(true),
email: Random.email(),
state: Random.boolean(),
salenumber: Random.integer(10, 100),
receivable: Random.integer(10, 100),
}, ],
};
orderList.push(item);
}
return {
total: 60,
data: obj.size == 10 ? orderList.slice(obj.size * obj.page - obj.size, obj.size * obj.page) : orderList,
};
};
export const homeList = (options) => {
let homeList = [];
for (let index = 0; index < 10; index++) {
let obj = {
name: "限时秒杀",
title_id: Random.integer(0, 10) + "id",
type: Random.boolean(),
number: Random.integer(10, 100),
order_number: Random.integer(100, 1000),
GWV_number: Random.integer(100, 5000),
state: Random.boolean(),
};
homeList.push(obj);
}
return {
data: homeList,
};
};
export const noticeLists = (options) => {
let noticeLists = [];
for (let index = 0; index < 10; index++) {
let item = {
text: Random.csentence(),
};
noticeLists.push(item);
}
return {
data: noticeLists,
};
};
export const cardlists = (options) => {
let obj = JSON.parse(options.body);
let cardlists = [];
for (let i = 0; i < 50; i++) {
let item = {
title: Random.csentence(5, 8), // Random.csentence( min, max )
user_name: Random.cname(), // Random.cname() 随机生成一个常见的中文姓名
email: Random.email(),
number: '$' + Random.integer(100, 5000) + '.00',
date: Random.date(),
pay_state: '100' + Random.integer(1, 3),
content: Random.csentence(),
};
cardlists.push(item);
}
return {
data: cardlists,
};
};
+32
View File
@@ -0,0 +1,32 @@
import router from "./router.js";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import store from "../store";
NProgress.configure({
ease: "ease",
speed: 500,
});
const writeNames = ["/login"];
router.beforeEach((to, from, next) => {
NProgress.start();
console.log(store.getters);
if (sessionStorage.getItem("token")) {
if (to.path === "/login") {
next("/");
}
return next();
} else {
if (writeNames.includes(to.path)) {
next();
} else {
next("/login");
}
}
});
router.afterEach((transition) => {
NProgress.done();
});
export default router;
+494
View File
@@ -0,0 +1,494 @@
// 模板自带的功能路由表
import login from "../views/login/index.vue";
import Layout from "../layout/index.vue";
const routerList = [
{
path: "/login",
name: login,
component: login,
},
{
path: "/",
component: Layout,
name: "container",
redirect: "home",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "首页",
},
children: [
{
path: "/home",
name: "home",
component: () => import("../views/home/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "首页",
},
},
],
},
{
path: "/form",
name: "form",
component: Layout,
meta: {
name: "表单页",
},
children: [
{
path: "/baseForm",
name: "baseForm",
component: () => import("../views/form/baseForm.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "基础表单",
},
},
{
path: "/stepFrom",
name: "stepFrom",
component: () => import("../views/form/stepFrom.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "分页表单",
},
},
{
path: "/advancedForm",
name: "advancedForm",
component: () => import("../views/form/advancedForm.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "高级表单",
},
},
],
},
{
path: "/system",
name: "system",
component: Layout,
meta: {
name: "系统管理",
},
children: [
{
path: "/Department",
name: "Department",
component: () => import("../views/system/Department/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "基础表格",
},
},
{
path: "/UserList",
name: "UserList",
component: () => import("../views/system/UserList/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "内嵌表格",
},
},
{
path: "/RoleList",
name: "RoleList",
component: () => import("../views/system/RoleList/index.vue"),
meta: {
requiresAuth: true,
name: "滑动加载",
},
},
{
path: "/MenuList",
name: "MenuList",
component: () => import("../views/system/MenuList/index.vue"),
meta: {
requiresAuth: true,
name: "可编辑Table",
},
},
{
path: "/importExcel",
name: "importExcel",
component: () => import("../views/system/Excel/importExcel.vue"),
meta: {
requiresAuth: true,
name: "Excel",
},
},
],
},
{
path: "/ErrorMessage",
name: "ErrorMessage",
component: Layout,
meta: {
name: "异常页面",
},
children: [
{
path: "/404",
name: "404",
component: () => import("../views/ErrorMessage/404.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "404",
},
},
{
path: "/500",
name: "500",
component: () => import("../views/ErrorMessage/500.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "500",
},
},
],
},
{
path: "/goods",
name: "goods",
component: Layout,
meta: {
name: "列表页",
},
children: [
{
path: "/goodCategory",
name: "goodCategory",
component: () => import("../views/goods/goodCategory.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "基础列表",
},
},
{
path: "/cardList",
name: "cardList",
component: () => import("../views/goods/cardList.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "卡片列表",
},
},
{
path: "/searchList",
name: "searchList",
component: () => import("../views/goods/searchList.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "搜索列表",
},
},
],
},
{
path: "/able",
name: "able",
component: Layout,
meta: {
name: "功能",
},
children: [
{
path: "/watermark",
name: "watermark",
component: () => import("../views/able/watermark.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "水印",
},
},
{
path: "/countTo",
name: "countTo",
component: () => import("../views/able/countTo.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "数字动画",
},
},
{
path: "/batchImport",
name: "batchImport",
component: () => import("../views/able/batchImport.vue"),
meta: {
requiresAuth: true,
name: "图片上传",
},
},
{
path: "/fileImport",
name: "fileImport",
component: () => import("../views/able/fileImport.vue"),
meta: {
requiresAuth: true,
name: "文件上传",
},
},
{
path: "/wangEditor",
name: "wangEditor",
component: () => import("../views/able/wangEditor.vue"),
meta: {
requiresAuth: true,
name: "富文本编辑器",
},
},
{
path: "/markdown",
name: "markdown",
component: () => import("../views/able/markdown.vue"),
meta: {
requiresAuth: true,
name: "markdown",
},
},
{
path: "/strength",
name: "strength",
component: () => import("../views/able/strength.vue"),
meta: {
requiresAuth: true,
name: "密码强度",
},
},
{
path: "/validation",
name: "validation",
component: () => import("../views/able/validation.vue"),
meta: {
requiresAuth: true,
name: "验证组件",
},
},
{
path: "/guide",
name: "guide",
component: () => import("../views/able/guide.vue"),
meta: {
requiresAuth: true,
name: "引导页",
},
},
{
path: "/embedded",
name: "embedded",
component: () => import("../views/able/embedded.vue"),
meta: {
requiresAuth: true,
name: "内嵌页",
},
},
],
},
{
path: "/flow",
name: "flow",
component: Layout,
meta: {
name: "图形编辑器",
},
children: [
{
path: "/flowCat",
name: "flowCat",
component: () => import("../views/flow/flowCat.vue"),
meta: {
requiresAuth: true,
name: "流程图",
},
},
],
},
{
path: "/echarts",
name: "echarts",
component: Layout,
meta: {
name: "图表",
},
children: [
{
path: "/baidumap",
name: "baidumap",
component: () => import("../views/echarts/map/baidumap.vue"),
meta: {
requiresAuth: true,
name: "百度地图",
},
},
{
path: "/gaodemap",
name: "gaodemap",
component: () => import("../views/echarts/map/gaodemap.vue"),
meta: {
requiresAuth: true,
name: "高德地图",
},
},
{
path: "/histogram",
name: "histogram",
component: () => import("../views/echarts/histogram.vue"),
mate: {
requiresAuth: true,
name: "柱状图",
},
},
{
path: "/line",
name: "line",
component: () => import("../views/echarts/line.vue"),
meta: {
requiresAuth: true,
name: "折线图",
},
},
{
path: "/radar",
name: "radar",
component: () => import("../views/echarts/radar.vue"),
meta: {
requiresAuth: true,
name: "雷达图",
},
},
],
},
{
path: "/videoS",
name: "videoS",
component: Layout,
meta: {
name: "视频播放器",
},
children: [
{
path: "/video",
name: "video",
component: () => import("../views/video/index.vue"),
meta: {
requiresAuth: true,
name: "视频播放器",
},
},
],
},
{
path: "/DataReport",
name: "DataReport",
component: Layout,
meta: {
name: "数据统计",
},
children: [
{
path: "/demo1",
name: "demo1",
component: () => import("../views/DataReport/demo1.vue"),
meta: {
requiresAuth: true,
name: "项目一",
},
},
],
},
{
path: "/material",
name: "material",
component: Layout,
meta: {
name: "素材管理",
},
children: [
{
path: "/materialIndex",
name: "materialIndex",
component: () => import("../views/material/materialIndex.vue"),
meta: {
requiresAuth: true,
name: "素材管理",
},
},
],
},
{
path: "/directives",
name: "directives",
component: Layout,
meta: {
name: "自定义指令",
},
children: [
{
path: "/Drag",
name: "Drag",
component: () => import("../views/directives/Drag.vue"),
meta: {
requiresAuth: true,
name: "拖拽",
},
},
{
path: "/throttle",
name: "throttle",
component: () => import("../views/directives/throttle.vue"),
meta: {
requiresAuth: true,
name: "节流",
},
},
{
path: "/copy",
name: "copy",
component: () => import("../views/directives/copy.vue"),
meta: {
requiresAuth: true,
name: "复制",
},
},
{
path: "/debounceDirect",
name: "debounceDirect",
component: () => import("../views/directives/debounceDirect.vue"),
meta: {
requiresAuth: true,
name: "防抖",
},
},
{
path: "/longPress",
name: "longPress",
component: () => import("../views/directives/longPress.vue"),
meta: {
requiresAuth: true,
name: "长按指令",
},
},
],
},
{
path: "/users",
name: "users",
component: Layout,
meta: {
name: "个人中心",
},
children: [
{
path: "/user",
name: "user",
component: () => import("../views/users/index.vue"),
meta: {
requiresAuth: true,
name: "关于我",
},
},
],
},
];
export default routerList;
+15
View File
@@ -0,0 +1,15 @@
import { createRouter, createWebHashHistory } from "vue-router";
import home from "../views/home/index.vue";
import login from "../views/login/index.vue";
import Layout from "../layout/index.vue";
// 原本模板功能路由表
import routerList from "./originalRoutingTable.js";
const routes = routerList;
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
+9
View File
@@ -0,0 +1,9 @@
const getters = {
token: (state) => state.user.token,
isCollapse: (state) => state.user.isCollapse,
UserInfo: (state) => state.user.UserInfo,
themeConfig: (state) => state.user.themeConfig,
tabsMenuList: (state) => state.tabs.tabsMenuList
};
export default getters;
+23
View File
@@ -0,0 +1,23 @@
import {
createStore
} from "vuex";
import user from "./modules/users";
import tabs from './modules/tabs'
import getters from "./getters";
import createPersistedState from "vuex-persistedstate";
export default createStore({
getters,
modules: {
user,
tabs
},
plugins: [
// 默认储存在localstorage
createPersistedState({
// 本地储存名
key: "user",
// 指定模块
paths: ["user"],
}),
],
});
+69
View File
@@ -0,0 +1,69 @@
import {
ElStep
} from "element-plus";
import router from "../../router/router.js";
export default {
namespaced: true,
state: {
tabsMenuList: [{
title: "首页",
url: 'home',
icon: "Menu",
close: false,
checked: true
}]
},
mutations: {
addTabsMenu(state, obj) {
let titles = state.tabsMenuList.map(item => item.title).join(',')
if (titles.includes(obj.title)) {
for (let index = 0; index < state.tabsMenuList.length; index++) {
const element = state.tabsMenuList[index];
state.tabsMenuList[index].checked = false
if (element.title == obj.title) {
state.tabsMenuList[index].checked = true
}
}
} else {
state.tabsMenuList.forEach(item => {
item.checked = false
});
state.tabsMenuList.push(obj)
}
},
delectTagMenu(state, item) {
if (state.tabsMenuList.length == 0) {
return
}
for (let i = 0; i < state.tabsMenuList.length; i++) {
let ele = state.tabsMenuList[i]
console.log(ele)
if (ele == item) {
state.tabsMenuList[i - 1].checked = true
state.tabsMenuList.splice(i, 1)
return router.push(state.tabsMenuList[i - 1].url)
}
}
console.log(state.tabsMenuList)
}
},
actions: {
addTabs({
commit
}, str) {
console.log(str);
commit("addTabsMenu", str);
},
delectTag({
commit
}, val) {
commit('delectTagMenu', val)
}
},
};
+113
View File
@@ -0,0 +1,113 @@
import { LoginInfo } from "../../api/modules/index.js";
import router from "../../router/router.js";
import { mix } from "../../utils/color.js";
import { ElMessage } from "element-plus";
export default {
namespaced: true,
state: {
UserInfo: {},
token: sessionStorage.getItem("token") || "",
isCollapse: true,
themeConfig: {
primary: "#4060c7",
tabColor: "#FFFFFF",
footColor: "#606266",
backgroundColor: "#FFFFFF",
textColor: "#00000099",
istags: true,
},
},
mutations: {
setToken(state, token) {
state.token = token;
},
setUserInfo(state, userinfo) {
state.UserInfo = userinfo;
},
SetIsCollapse(state, isCollapse) {
state.isCollapse = isCollapse;
},
setThemeConfig(state, primary) {
state.themeConfig.primary = primary;
},
setThemeConfigTbaColor(state, primary) {
state.themeConfig.tabColor = primary;
if (primary == "#FFFFFF") {
state.themeConfig.footColor = "#606266";
} else {
state.themeConfig.footColor = "#ffffff";
}
},
setThemeConfigMenuColor(state, primary) {
if (primary) {
state.themeConfig.backgroundColor = "#FFFFFF";
state.themeConfig.textColor = "#00000099";
} else {
state.themeConfig.backgroundColor = "#1d2129";
state.themeConfig.textColor = "#bdbdc0";
}
},
setThemeConfigchangeTags(state, primary) {
if (primary) {
state.themeConfig.istags = true;
} else {
state.themeConfig.istags = false;
}
},
},
actions: {
// 登录
login({ commit }, userInfo) {
return new Promise((resolve, reject) => {
LoginInfo(userInfo)
.then((res) => {
console.log(res.data.data);
sessionStorage.setItem("token", res.data.data.token);
sessionStorage.setItem("UserInfo", JSON.stringify(res.data.data));
commit("setToken", res.data.data.token);
commit("setUserInfo", res.data.data);
router.replace("/");
ElMessage({
message: "登录成功",
type: "success",
});
resolve();
})
.catch((err) => {
reject(err);
});
});
},
changeIsCollapse({ commit }, str) {
console.log(str);
commit("SetIsCollapse", str);
},
changeThem({ commit }, str) {
commit("setThemeConfig", str);
const pre = "--el-color-primary";
// 白色混合色
const mixWhite = "#ffffff";
// 黑色混合色
const mixBlack = "#000000";
const el = document.documentElement;
el.style.setProperty(pre, str);
// 这里是覆盖原有颜色的核心代码
for (let i = 1; i < 10; i += 1) {
el.style.setProperty(`${pre}-light-${i}`, mix(str, mixWhite, i * 0.1));
}
el.style.setProperty("--el-color-primary-dark", mix(str, mixBlack, 0.1));
},
changeTabColor({ commit }, val) {
commit("setThemeConfigTbaColor", val);
},
changeMenuColor({ commit }, val) {
commit("setThemeConfigMenuColor", val);
},
changeTags({ commit }, val) {
commit("setThemeConfigchangeTags", val);
},
},
};
+216
View File
@@ -0,0 +1,216 @@
/*
Reset style sheet
*/
html,
body {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
html,
body,
#app {
height: 100%;
width: 100%;
}
/* flex */
.flx-center {
display: flex;
justify-content: center;
align-items: center;
}
.flx-justify-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flx-align-center {
display: flex;
align-items: center;
}
.flx-row {
display: flex;
flex-flow: row;
align-items: center;
}
.flx-row-right {
display: flex;
flex-flow: row-reverse;
align-items: center;
}
.col-center {
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
align-items: center;
}
.ml10 {
margin-left: 10px;
}
.p10 {
padding: 10px;
}
.p20 {
padding: 20px;
}
.m10 {
margin: 10px;
}
.m20 {
margin: 20px;
}
.mt10 {
margin-top: 10px;
}
.mt20 {
margin-top: 20px;
}
.w100 {
width: 100%;
}
.ml20 {
margin-left: 20px;
}
.mR10 {
margin-right: 10px;
}
.one-cut-txt {
/*强制文字在一行文本框内*/
white-space: nowrap;
/*溢出部分文字隐藏*/
overflow: hidden;
/*溢出部分省略号处理*/
text-overflow: ellipsis;
}
/* 清除浮动 */
.clearfix::after {
content: "";
display: block;
height: 0px;
clear: both;
overflow: hidden;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.2s;
}
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
transition: all 0.2s;
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
transition: all 0.2s;
}
/* Breadcrumb */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.2s ease;
}
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(10px);
}
.breadcrumb-leave-active {
position: absolute;
z-index: -1;
}
/* scroll bar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: white;
}
::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 0px white;
-webkit-box-shadow: inset 0 0 0px white;
background-color: #dddee0;
border-radius: 20px;
}
.content-box {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
font-size: 23px;
font-weight: bold;
color: rgb(88, 88, 88);
line-height: 100px;
min-height: 400px;
}
+16
View File
@@ -0,0 +1,16 @@
export const mix = (c1, c2, ratio) => {
ratio = Math.max(Math.min(Number(ratio), 1), 0)
let r1 = parseInt(c1.substring(1, 3), 16)
let g1 = parseInt(c1.substring(3, 5), 16)
let b1 = parseInt(c1.substring(5, 7), 16)
let r2 = parseInt(c2.substring(1, 3), 16)
let g2 = parseInt(c2.substring(3, 5), 16)
let b2 = parseInt(c2.substring(5, 7), 16)
let r = Math.round(r1 * (1 - ratio) + r2 * ratio) + ''
let g = Math.round(g1 * (1 - ratio) + g2 * ratio) + ''
let b = Math.round(b1 * (1 - ratio) + b2 * ratio) + ''
r = ('0' + (r || 0).toString(16)).slice(-2)
g = ('0' + (g || 0).toString(16)).slice(-2)
b = ('0' + (b || 0).toString(16)).slice(-2)
return '#' + r + g + b
}
+44
View File
@@ -0,0 +1,44 @@
export function getDateTime(type) {
var date = new Date();
var hengGang = "-";
var maoHao = ":";
var year = date.getFullYear();
var month = date.getMonth() + 1;
var curDate = date.getDate();
var curHours = date.getHours();
var curMinutes = date.getMinutes();
var curSeconds = date.getSeconds();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (curDate >= 0 && curDate <= 9) {
curDate = "0" + curDate;
}
if (curHours >= 0 && curHours <= 9) {
curHours = "0" + curHours;
}
if (curMinutes >= 0 && curMinutes <= 9) {
curMinutes = "0" + curMinutes;
}
if (curSeconds >= 0 && curSeconds <= 9) {
curSeconds = "0" + curSeconds;
}
var currentdate = "";
if (type == "year") {
currentdate = year;
return currentdate;
} else if (type == "month") {
currentdate = year + hengGang + month;
return currentdate;
} else {
currentdate = year + hengGang + month + hengGang + curDate + " "
return currentdate;
}
}
// var year = getDateTime('year');
// console.log(year); // 2021
// var month = getDateTime('month');
// console.log(month); // 12
// var date = getDateTime('');
// console.log(date); // 2021-12-03 09:00:00
+24
View File
@@ -0,0 +1,24 @@
<template>
<div class="content-box" style="height: 100%">
<iframe
src="http://124.221.156.158:3031/#/ScreenPage"
frameborder="0"
class="full-iframe"
></iframe>
</div>
</template>
<style lang="scss" scoped>
.content-box {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
background-color: #fff;
}
.full-iframe {
// margin-top: 2.5%;
width: 100%;
height: 100%;
}
</style>
View File
+1
View File
@@ -0,0 +1 @@
<template>数据统计</template>
+456
View File
@@ -0,0 +1,456 @@
<template>
<div class="col-center">
<svg
width="400px"
height="300px"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle opacity=".3" cx="33.329" cy="107.131" r="5.356" fill="#4060c7" />
<path
d="M373.107 238.514h-13.863c-1.471-7.141-2.584-21.424 4.726-21.424 7.309 0 9.137 14.283 9.137 21.424Z"
fill="#E2E5EC"
/>
<path
d="M364.151 221.816c.84 1.785 2.52 7.499 2.52 16.068"
stroke="#FFFEFE"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M355.958 238.514H341.78c1.786-4.096 6.365-12.162 10.398-11.657 4.032.504 4.2 7.981 3.78 11.657Z"
fill="#E2E5EC"
/>
<path
d="M349.657 232.843c-.525 1.576-1.575 5.041-1.575 6.302"
stroke="#FFFEFE"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
opacity=".6"
d="M34.84 201.221c-40.337-44.164 20.284-44.435 23.447-67.198 7.684-55.319 57.32-93.027 111.792-79.099 73.24 18.719 67.033 76.611 129.865 64.354 80.714-15.706 114.15 37.333 51.403 94.438-32.732 29.792 19.258 25.667 19.258 25.667l-294.482 1.022c-34.78-30.41 7.822-1.098-17.836-10.077-32.063-11.201 3.097-.046-23.447-29.107Z"
fill="url(#a)"
/>
<path
d="M22.301 239.144h350.356M376.932 239.144h5.986"
stroke="#011650"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M50.177 238.514h-25.34C18.878 228.2 9.95 207.011 21.901 204.77c14.938-2.8 18.806 16.005 18.806 18.406 7.682-2.454 9.514 9.203 9.47 15.338Z"
fill="#E2E5EC"
/>
<path
d="M25.966 212.639c1.823 2.934 6.037 11.17 10.198 24.615M43.572 227.177l-3.334 6.936"
stroke="#fff"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M128.521 238.514h-15.157v-13.863h-45.49c-4.55 0-7.6-4.673-5.768-8.838l50.548-114.947a10.083 10.083 0 0 1 9.229-6.023h.336a6.301 6.301 0 0 1 6.302 6.301v109.3h25.425a7.103 7.103 0 0 1 7.103 7.103 7.103 7.103 0 0 1-7.103 7.104h-25.425v13.863Zm-15.157-109.101-34.98 81.031h34.98v-81.031ZM160.419 167.918c0-18.586 3.76-34.942 11.279-49.067 7.637-14.126 16.39-21.189 26.26-21.189 9.869 0 18.622 7.063 26.259 21.189 7.636 14.125 11.455 30.543 11.455 49.253s-3.819 35.128-11.455 49.254c-7.637 14.125-16.39 21.188-26.259 21.188-9.87 0-18.623-7.063-26.26-21.188-7.519-14.126-11.279-30.606-11.279-49.44Zm16.214-1.074c0 17.223 2.232 32.051 6.697 41.964 4.465 9.789 9.34 14.683 14.628 14.683 5.287 0 10.162-4.894 14.627-14.683 4.582-9.913 6.873-23.543 6.873-40.89 0-17.719-2.291-31.348-6.873-40.889-4.465-9.541-9.34-14.312-14.627-14.312-5.288 0-10.163 4.771-14.628 14.312s-6.697 21.972-6.697 39.815ZM308.986 238.514H293.83v-13.863h-45.9c-4.557 0-7.607-4.688-5.761-8.854L293.11 100.84a10.083 10.083 0 0 1 9.218-5.997h.357a6.301 6.301 0 0 1 6.301 6.301v109.3h22.856a7.103 7.103 0 0 1 7.103 7.103 7.103 7.103 0 0 1-7.103 7.104h-22.856v13.863ZM293.83 129.413l-35.227 81.031h35.227v-81.031Z"
fill="#CDDAFE"
/>
<mask
id="b"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="61"
y="94"
width="278"
height="145"
>
<path
d="M128.521 238.514h-15.156v-13.863h-45.49c-4.55 0-7.6-4.673-5.769-8.838l53.198-120.97h13.217v115.601h25.425a7.103 7.103 0 1 1 0 14.207h-25.425v13.863Zm-15.156-109.101-34.98 81.031h34.98v-81.031ZM160.42 167.918c0-18.586 3.76-34.942 11.279-49.067 7.637-14.126 16.39-21.189 26.259-21.189s18.622 7.063 26.259 21.189c7.637 14.125 11.455 30.543 11.455 49.253s-3.818 35.128-11.455 49.254c-7.637 14.125-16.39 21.188-26.259 21.188s-18.622-7.063-26.259-21.188c-7.519-14.126-11.279-30.606-11.279-49.44Zm16.214.186c0 17.223 2.232 30.791 6.696 40.704 4.465 9.789 9.341 14.683 14.628 14.683 5.287 0 10.163-4.894 14.628-14.683 4.582-9.913 6.873-23.543 6.873-40.89 0-17.719-2.291-31.348-6.873-40.889-4.465-9.541-9.341-14.312-14.628-14.312-5.287 0-10.163 4.771-14.628 14.312-4.464 9.541-6.696 23.232-6.696 41.075ZM308.986 238.514H293.83v-13.863h-45.9c-4.557 0-7.607-4.688-5.761-8.854l53.6-120.954h13.217v115.601h22.856a7.104 7.104 0 1 1 0 14.207h-22.856v13.863ZM293.83 129.413l-35.227 81.031h35.227v-81.031Z"
fill="#CDDAFE"
/>
</mask>
<g mask="url(#b)">
<ellipse
opacity=".8"
cx="198.741"
cy="177.076"
rx="44.11"
ry="63.329"
transform="rotate(-180 198.741 177.076)"
fill="url(#c)"
/>
<ellipse
cx="197.48"
cy="201.651"
rx="44.11"
ry="57.027"
fill="url(#d)"
/>
<ellipse
opacity=".6"
cx="54.998"
cy="195.995"
rx="44.11"
ry="58.603"
transform="rotate(22.14 54.998 195.995)"
fill="url(#e)"
/>
<ellipse
opacity=".6"
cx="247.196"
cy="226.466"
rx="19.626"
ry="58.603"
transform="rotate(22.14 247.196 226.466)"
fill="url(#f)"
/>
<path
d="m71.138 224.966 7.561-14.808h34.658v14.808h-42.22Z"
fill="#8BD9FC"
/>
<mask
id="g"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="71"
y="210"
width="43"
height="15"
>
<path
d="m71.138 224.966 7.561-14.808h34.658v14.808h-42.22Z"
fill="#8BD9FC"
/>
</mask>
<g mask="url(#g)">
<ellipse
rx="20.794"
ry="31.822"
transform="matrix(-1 0 0 1 74.603 213.624)"
fill="url(#h)"
style="mix-blend-mode: multiply"
opacity=".6"
/>
</g>
<path
d="m251.042 225.281 7.715-15.123h35.134v15.123h-42.849Z"
fill="#8BD9FC"
/>
<mask
id="i"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="251"
y="210"
width="43"
height="16"
>
<path
d="m251.042 225.281 7.715-15.123h35.134v15.123h-42.849Z"
fill="#8BD9FC"
/>
</mask>
<g mask="url(#i)">
<ellipse
rx="20.794"
ry="31.822"
transform="matrix(-1 0 0 1 255.453 213.624)"
fill="url(#j)"
style="mix-blend-mode: multiply"
opacity=".6"
/>
</g>
<path
d="M128.48 210.158h25.521a7.248 7.248 0 0 1 0 14.493H128.48v-14.493ZM309.014 210.158h23a7.248 7.248 0 0 1 0 14.493h-23v-14.493Z"
fill="#8BD9FC"
/>
<path
d="M128.48 224.651v-14.178l6.932 14.178h-6.932ZM309.014 224.651v-14.178l6.932 14.178h-6.932Z"
fill="#6DBADD"
/>
<path
d="m113.042 129.815 15.438-29.931v29.931h-15.438Z"
fill="url(#k)"
/>
<path
d="m293.891 129.815 15.123-29.616.315 29.616h-15.438Z"
fill="url(#l)"
/>
<path
d="M114.016 94.493c.371 4.57-2.625 17.776-11.171 34.503-10.683 20.91-39.795 91.472-41.474 97.545-1.343 4.859 2.267 4.876 3.78 0 .997-3.211 25.43-63.343 35.139-77.657 12.137-17.893 13.825-42.923 19.528-54.095 5.703-11.172 7.848-4.138 9.239-2.677 1.266 1.33 1.996-21.022-1.682-9.29-4.958 8.871-18.931 70.821-28.511 81.651-7.247 8.191-23.031 41.427-19.85 45.685M295.753 91.063c.371 4.57-2.453 19.504-10.999 36.232-10.683 20.91-39.967 89.743-41.646 95.817-1.343 4.859 2.268 4.876 3.781 0 .996-3.211 25.429-63.343 35.138-77.657 12.137-17.893 13.825-42.923 19.528-54.095 5.703-11.172 7.848-4.138 9.239-2.677 1.266 1.33 1.997-21.022-1.682-9.29-4.958 8.871-19.819 71.415-29.399 82.244-7.247 8.192-31.223 60.647-28.041 64.905"
stroke="#fff"
stroke-width=".441"
/>
<circle
opacity=".3"
cx="208.192"
cy="118.788"
r="4.411"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="214.179"
cy="130.446"
r=".945"
fill="#758CD6"
/>
<circle opacity=".3" cx="214.494" cy="109.966" r=".63" fill="#758CD6" />
<circle
opacity=".3"
cx="229.932"
cy="132.336"
r="1.575"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="230.877"
cy="147.144"
r="1.26"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="218.905"
cy="138.952"
r="5.041"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="227.727"
cy="117.528"
r="6.301"
fill="#758CD6"
/>
</g>
<path
d="M373.918 77.64c1.524.761 2.221 2.468-.367 2.835m-2.469 0c-6.617-.63-26.781-6.617-35.288-14.808"
stroke="#F6BCD1"
stroke-width=".945"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M363.836 56.53c-12.099-4.538-18.484 3.15-20.165 7.56.42.841 2.836 4.412 10.082 7.563 7.031 3.056 13.863 3.36 16.699 3.15 1.575-4.2 2.458-13.737-6.616-18.274Z"
fill="#F6BCD1"
/>
<path
d="M359.424 61.57c-1.105-.553-2.835-.82-4.096 2.206-.504 1.26.21 3.045.631 3.78-1.051 0-3.214.378-3.466 1.89 0 .316 0 .63 1.575 1.261 1.26.504 2.416 1.05 2.836 1.26.21.105.567.378.315.63-.209.21-.577.144-.816.056l-.129-.056c.035.018.079.038.129.056l1.698.728c.065.235.249.226.378.162l-.378-.162c-.041-.149-.034-.394.063-.784.315-1.26.945-3.465-.945-4.41.63 0 2.52-.316 3.151-2.521.63-2.206.315-3.466-.946-4.096Z"
fill="#fff"
/>
<path
d="M341.466 61.255c-3.256-.525-9.263-.82-9.768 2.206-.504 3.024 7.247 9.767 20.795 14.808 15.159 5.64 25.881 5.986 26.781 2.835.9-3.15-1.891-5.04-4.726-6.616"
stroke="#F6BCD1"
stroke-width="1.26"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
stroke="#F6BCD1"
stroke-width=".945"
stroke-linecap="round"
stroke-linejoin="round"
d="m374.516 60.215-1.764 1.323M372.642 57.963l-1.103 1.91M375.965 62.988h-2.205"
/>
<path
d="m344.616 81.61-6.301 4.726M356.235 85.748v9.095"
stroke="#F6BCD1"
stroke-width="1.26"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M182.185 184.416c3.269 2.569 13.913 20.531 18.973 29.19l-14.887 7.882c-1.07-1.265-4.32-6.072-7.589-9.341 1.167 3.269 2.53 18.584 2.919 25.103a3067.97 3067.97 0 0 0-16.347 3.211c-1.634-2.101-2.335-22.476-9.049-29.774l-3.502-3.21 2.043-11.677 20.141-14.886c1.07.097 4.028.934 7.298 3.502Z"
fill="#5783FC"
/>
<path
d="m192.438 232.212 23.315-11.599c-1.459-2.336-5.546-1.752-7.881-1.752-1.868 0-5.254-3.503-6.714-5.254l-15.179 7.589 6.459 11.016Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".584"
d="m199.869 217.319 3.503-1.751M200.584 218.747l3.956-2.012"
/>
<path
d="M164.688 250.454h27.129c-.247-2.743-4.157-4.067-6.241-5.122-1.667-.843-3.756-5.86-4.267-8.081l-16.054 3.211-.567 9.992Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".584"
d="m179.258 241.452 3.884-.505M179.464 243.036l4.398-.603"
/>
<path
d="M118.55 210.104c-6.538-.934-7.005-11.481-6.422-16.638l63.051-14.303v1.751l-9.341 5.254-1.751 4.087 3.211 1.459 1.167 4.087c-2.53-.195-8.29.175-11.092 3.211-2.802 3.036-1.946 9.049-1.168 11.676-9.827.194-31.116.35-37.655-.584Z"
fill="#5783FC"
/>
<path
d="M178.098 211.272c-4.379-3.795-7.998-11.968-9.049-14.595-.584-1.46-1.557-3.99-1.751-4.963-.682-.194-2.161-.7-2.628-1.167"
stroke="#000"
stroke-width=".584"
stroke-linecap="round"
/>
<path
d="m175.179 183.011-13.271 9.341h-14.173c-1.09 0-1.586-1.361-.751-2.062l4.843-4.068 11.649-2.627 1.648-9.2a2.916 2.916 0 0 1 1.799-2.199l10.73-4.249a.584.584 0 0 1 .784.672l-3.258 14.392Z"
fill="#66DED2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M133.145 156.739a8.174 8.174 0 1 0-6.196-13.502 4.086 4.086 0 1 0-1.642 7.651 8.175 8.175 0 0 0 7.838 5.851Z"
fill="#FC466F"
/>
<path
d="M146.281 140.685c0-6.305-5.449-5.741-8.173-4.67-3.211-3.503-7.298 1.167-8.174 2.335-1.751 0-2.627.292-3.794 1.459-.934.934-1.168 3.114-1.168 4.087.876.194 2.627.759 2.627 1.459-.233 1.401.292 1.946.584 2.044.876 0 1.053-1.408 1.46-2.627.875-2.628 5.254-.973 7.005 0 3.211 1.264 9.633 2.218 9.633-4.087Z"
fill="#202020"
/>
<path
d="M131.686 158.199c-12.61-1.635-18.682 6.519-20.141 10.8-1.168 3.016-3.62 10.45-4.087 16.055-.584 7.005 1.46 12.551 14.011 13.427 10.042.701 21.115-4.573 25.396-7.297l4.962-7.59c4.184-1.946 12.085-5.955 10.217-6.422-1.869-.467-8.757-.973-11.968-1.167-.487-.876-1.81-3.503-3.211-7.006-1.752-4.378-5.838-9.633-15.179-10.8Z"
fill="#011650"
/>
<path
d="M146.865 191.184c-1.635-4.204-1.071-10.217-.584-12.552 8.465-.876 10.8 0 11.676.292.233-.701 1.459-1.46 3.211-1.752 1.751-.292 3.503.584 3.503.584l-1.46 7.59c-.194.097-.7.233-1.167 0 .467 2.802-.973 3.113-1.752 2.919-.584.291-2.043.875-3.211.875-2.919.876-4.378-.292-4.962-.875-.584-.584-1.46-.876-2.043 0-.584.875-2.044 2.043-3.211 2.919Z"
fill="#FC466F"
/>
<path
d="m125.264 169 .292 11.092c4.379-.487 14.362-1.46 19.265-1.46M141.027 167.832c.876 1.751 2.627 5.429 2.627 6.13"
stroke="#fff"
stroke-width=".584"
stroke-linecap="round"
/>
<path
d="M153.287 182.427c1.167.973 3.502 3.62 3.502 6.422M157.081 180.967c1.07.973 3.153 3.737 2.919 7.006M158.833 179.799c.973.973 2.977 3.211 3.21 4.379M162.627 179.507c.389.487 1.168 1.635 1.168 2.336"
stroke="#000"
stroke-width=".584"
stroke-linecap="round"
/>
<ellipse
cx="170.801"
cy="178.34"
rx=".876"
ry="1.751"
transform="rotate(7.122 170.801 178.34)"
fill="#fff"
/>
<path
d="m180.931 155.117 4.196-2.427c.538-.311 1.154.264.881.823l-1.492 3.053 2.348 2.433a.63.63 0 0 1-.187 1.009l-5.155 2.401c-.629.293-1.201-.497-.725-1.003l2.289-2.435-2.331-2.915a.63.63 0 0 1 .176-.939ZM174.853 143.68l2.382-2.976c.191-.239.575-.086.549.218l-.223 2.684 2.233 1.029a.31.31 0 0 1 .094.495l-3.207 3.349c-.236.246-.637-.013-.51-.329l1.078-2.672-2.31-1.338a.308.308 0 0 1-.086-.46Z"
fill="#5783FC"
/>
<defs>
<linearGradient
id="a"
x1="201.893"
y1="108.609"
x2="201.893"
y2="240.405"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F7F4FD" />
<stop offset="1" stop-color="#F7F4FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1="186.768"
y1="113.747"
x2="182.306"
y2="188.099"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#ABE7FF" />
<stop offset="1" stop-color="#ADE4FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1="194.645"
y1="251.117"
x2="202.045"
y2="197.234"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1="47.309"
y1="221.217"
x2="99.469"
y2="200.52"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1="247.196"
y1="240.392"
x2="272.201"
y2="230.641"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="h"
x1="28.671"
y1="18.904"
x2="3.896"
y2="20.454"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="j"
x1="28.671"
y1="18.904"
x2="3.896"
y2="20.454"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#6DBADD" />
<stop offset="1" stop-color="#8BD9FC" stop-opacity="0" />
</linearGradient>
<linearGradient
id="k"
x1="120.761"
y1="94.843"
x2="120.761"
y2="129.815"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="l"
x1="301.61"
y1="94.843"
x2="301.61"
y2="129.815"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
<el-button type="primary" @click="backUrl()">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const backUrl = () => {
router.push({
name: 'home',
query: {
// ...route.query,
},
})
}
</script>
+470
View File
@@ -0,0 +1,470 @@
<template>
<div class="col-center">
<svg
width="400px"
height="300px"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M371.128 249.745h-13.805c-1.464-7.112-2.573-21.335 4.706-21.335s9.099 14.223 9.099 21.335Z"
fill="#E2E5EC"
/>
<path
d="M362.209 233.116c.837 1.778 2.51 7.467 2.51 16.002"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M352.797 249.745h-14.119c1.778-4.079 6.338-12.111 10.354-11.609 4.016.502 4.183 7.948 3.765 11.609Z"
fill="#E2E5EC"
/>
<path
d="M347.777 244.097c-.523 1.569-1.569 5.02-1.569 6.275"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
opacity=".6"
d="M369.442 211.845c40.308-44.132-20.269-44.402-23.43-67.15-7.679-55.278-57.279-92.958-111.711-79.04-73.187 18.705-66.984 76.555-129.771 64.306-80.655-15.694-112.281 27.948-49.58 85.012 32.708 29.77-21.03 35.007-21.03 35.007L328.19 251c34.754-30.387-7.816-1.097 17.823-10.069 32.039-11.193-3.095-.046 23.43-29.086Z"
fill="url(#a)"
/>
<path
d="M23.044 249.745h348.891M376.192 249.745h5.962"
stroke="#011650"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M115.867 160.108c10.348 0 18.894 3.771 25.637 11.312 6.86 7.541 10.29 17.987 10.29 31.339 0 12.857-3.895 23.797-11.685 32.822-7.674 9.024-17.964 13.537-30.869 13.537-10.697 0-18.952-3.524-24.765-10.57-5.814-7.047-8.72-15.144-8.72-24.292 0-9.148 14.432-11.26 14.998 0 0 7.252 1.686 11.188 5.058 14.649 3.488 3.462 7.964 5.192 13.429 5.192 7.441 0 13.719-2.719 18.835-8.159 5.116-5.563 7.674-13.289 7.674-23.179 0-9.396-1.919-16.319-5.755-20.769-3.837-4.574-8.546-6.861-14.127-6.861-5.697 0-10.522.927-14.475 2.781-3.837 1.731-6.453 4.265-7.849 7.603H79.94l4.186-73.961 49.941-4.119c5.475-.451 9.86 4.463 8.79 9.852a8.27 8.27 0 0 1-7.194 6.608l-38.98 4.348-2.093 38.172c6.976-4.203 14.068-6.305 21.277-6.305ZM164.874 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981ZM254.168 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981Z"
fill="#C9D7FF"
/>
<mask
id="b"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="75"
y="107"
width="254"
height="143"
>
<path
d="M115.867 160.108c10.348 0 18.894 3.771 25.637 11.312 6.86 7.541 10.29 17.987 10.29 31.339 0 12.857-3.895 23.797-11.685 32.822-7.674 9.024-17.964 13.537-30.869 13.537-10.697 0-18.952-3.524-24.765-10.57-5.814-7.047-8.72-15.144-8.72-24.292 0-9.148 14.432-11.26 14.998 0 .566 11.259 1.686 11.188 5.058 14.649 3.488 3.462 7.964 5.192 13.429 5.192 7.441 0 13.719-2.719 18.835-8.159 5.116-5.563 7.674-13.289 7.674-23.179 0-9.396-1.919-16.319-5.755-20.769-3.837-4.574-8.546-6.861-14.127-6.861-5.697 0-10.522.927-14.475 2.781-3.837 1.731-6.453 4.265-7.849 7.603H79.94l4.186-73.961 49.941-4.119c5.475-.451 9.86 4.463 8.79 9.852a8.27 8.27 0 0 1-7.194 6.608l-38.98 4.348-2.093 38.172c6.976-4.203 14.068-6.305 21.277-6.305ZM164.874 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981ZM254.168 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981Z"
fill="#B0C4FE"
/>
</mask>
<g mask="url(#b)">
<ellipse
opacity=".6"
cx="84.54"
cy="179.779"
rx="43.925"
ry="31.689"
fill="url(#c)"
/>
<ellipse
opacity=".8"
cx="196.235"
cy="162.836"
rx="43.925"
ry="56.789"
fill="url(#d)"
/>
<ellipse
cx="301.028"
cy="197.976"
rx="43.925"
ry="56.789"
fill="url(#e)"
/>
<ellipse
cx="208.158"
cy="197.976"
rx="43.925"
ry="56.789"
fill="url(#f)"
/>
<path
d="m97.717 109.498-.941 18.825 44.866-5.02 3.452-16.942-47.377 3.137Z"
fill="#8BD9FC"
/>
<ellipse
opacity=".6"
cx="137.25"
cy="118.284"
rx="20.708"
ry="31.689"
fill="url(#g)"
/>
<path
opacity=".2"
d="m126.896 127.696 5.878-13.664c1.55-3.258 3.901-2.815 7.059-.139 2.987 2.531 8.587 8.219 11.03 10.561"
stroke="#000"
stroke-width=".941"
/>
<circle
opacity=".3"
cx="172.39"
cy="139.932"
r="4.393"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="178.351"
cy="151.541"
r=".941"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="178.665"
cy="131.147"
r=".628"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="168.938"
cy="165.974"
r="1.569"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="196.235"
cy="121.108"
r="1.255"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="183.058"
cy="160.012"
r="5.02"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="191.842"
cy="131.775"
r="6.275"
fill="#758CD6"
/>
<path
d="m93.324 186.681 1.569-20.707c4.706-6.066 15.813-17.947 22.59-16.943 8.471 1.255 21.963 11.923 30.748 16.001 8.785 4.079 4.706 14.433 7.216 20.08 2.51 5.648 0 29.493 0 43.926 0 14.432-5.961 15.06-16.001 20.08-10.04 5.02-29.179 5.02-40.16 3.765-10.982-1.255-14.433-7.217-20.708-11.923-6.275-4.706-10.354-17.57-12.236-25.414-1.883-7.844 5.96-12.236 8.785-13.177 2.823-.942 18.51 4.078 20.08 7.53 1.569 3.451 11.922 13.491 13.805 13.491 1.506 0 11.922 1.255 16.942 1.883 2.092-6.38 6.338-19.453 6.589-20.708.251-1.255-1.987-11.4-3.137-16.315l-13.492-5.961-22.59 4.392Z"
fill="#CDDAFE"
/>
<rect
x="116.755"
y="136.972"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 116.755 136.972)"
fill="url(#h)"
/>
<ellipse
cx="72.617"
cy="235.94"
rx="43.925"
ry="31.375"
fill="url(#i)"
/>
<path
d="m97.09 184.798-2.51-18.511-1.255 21.963 3.765-3.452Z"
fill="#91A4D7"
style="mix-blend-mode: multiply"
/>
<path
d="m102.11 128.951-4.393-19.139-.941 19.139h5.334Z"
fill="#6DBADD"
/>
<path
d="M86.422 107.302c1.568 4.288 2.196 17.758-1.57 36.081-4.705 22.904-2.823 38.592-2.823 44.867 0 5.02 6.066 3.765 8.471 4.392.105-3.346-8.345-13.177-2.823-29.492 6.902-20.394 1.882-44.867 4.392-57.103 2.51-12.236 6.435-6.054 8.158-5.02 1.568.941-3.66-20.708-4.08-8.471-2.404 9.831-1.254 69.652-4.078 83.771-1.651 8.256 2.092 22.486 6.275 25.728"
stroke="#fff"
stroke-width=".439"
/>
</g>
<path
d="M328.679 179.346c0-1.973-.042-3.921-.127-5.843m-4.746-27.924c1.821 5.757 3.131 11.823 3.931 18.198"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="m121.707 131.292 8.678-16.152c2.165-3.291 4.408-3.664 8.382-.74 3.756 2.766 10.878 9.072 13.973 11.657"
stroke="#000"
stroke-width="1.122"
/>
<rect
x="118.638"
y="131.323"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 118.638 131.323)"
fill="#E4E4ED"
/>
<mask
id="j"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="118"
y="124"
width="44"
height="37"
>
<rect
x="118.638"
y="131.323"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 118.638 131.323)"
fill="#F0F3FA"
/>
</mask>
<g mask="url(#j)">
<circle
cx="153.152"
cy="151.414"
r="11.226"
transform="rotate(-9.889 153.152 151.414)"
fill="#7DB5FF"
/>
<circle
r="1.847"
transform="scale(1 -1) rotate(9.889 863.109 762.065)"
fill="#7DB5FF"
/>
<path
stroke="#E01E67"
stroke-width=".748"
stroke-linecap="round"
d="m125.215 138.74 13.542-2.36M126.191 144.339l13.542-2.36"
/>
</g>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M268.084 141.814a6.589 6.589 0 1 0-6.559-5.956 2.51 2.51 0 0 0 2.349 4.436 6.564 6.564 0 0 0 4.21 1.52Z"
fill="#FC466F"
/>
<path
d="M270.594 129.578c2.761-1.757 2.614-3.242 2.196-3.765-.753-1.004-1.987-.418-2.51 0-.418.418-1.631 1.255-3.137 1.255-1.883 0-3.765 1.255-4.707 1.883-2.51-3.452-5.333-4.393-9.098-2.824-3.012 1.255-3.347 5.334-3.138 7.216-2.719-.418-8.158.063-8.158 5.334 0 6.589 5.02 6.275 10.354 5.647 5.334-.627 7.217-5.02 7.53-7.53.314-2.51-.941-4.392-.627-5.02.314-.627.941-.313 1.255.314-.251 1.757.104 3.661.314 4.393l2.51-.942c.418.314 1.38.565 1.882-.941.628-1.882 1.883-2.824 5.334-5.02Z"
fill="#000"
/>
<path
d="m273.418 149.062-5.962.628-3.451-.628-1.255-4.738c.523-.104 2.657.191 5.648.628 4.078.596 5.02 1.255 5.961 2.196l-.941 1.914Z"
fill="#FC466F"
/>
<path
d="M236.708 205.82c5.02-10.542 8.995-23.427 10.354-28.551 7.635.313 23.28.941 24.786.941 1.883 3.765 8.158 18.825 10.041 24.786 1.506 4.769 6.275 23.009 8.471 31.062-4.184 1.359-13.052 4.329-15.06 5.333-.942-7.216-13.178-39.532-14.433-42.356-1.255 3.137-7.844 19.452-11.295 23.845-2.761 3.514-7.425 12.341-9.412 16.315l-14.433-6.589c1.569-3.869 5.961-14.244 10.981-24.786Z"
fill="#5783FC"
/>
<path
d="M264.946 189.504c-.418.419-1.632 1.255-3.138 1.255 0 2.51-.313 4.707-.941 6.275"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="m277.496 248.804-2.197-9.727 15.06-5.02a4.778 4.778 0 0 0 4.393 3.765c3.012.251 4.602 2.824 5.02 4.079l-22.276 6.903ZM241.728 249.745l-20.08-10.668 4.392-8.471c4.079 1.778 12.613 5.585 14.119 6.589-1.255 3.451.942 5.334 1.883 7.53.753 1.757.104 4.079-.314 5.02Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".628"
d="m287.082 237.542 3.765-1.883M287.85 239.077l4.252-2.163M236.531 237.425l3.858 1.685M235.844 238.998l4.384 1.88"
/>
<path
d="m243.925 159.698-9.727-10.353c4.393-3.765 8.158-5.02 15.06-5.648 5.522-.502 11.714.209 14.119.628.105 1.359 1.13 4.204 4.393 4.706 3.263.502 5.333-1.464 5.961-2.51 1.778 1.464 5.898 5.396 8.157 9.412 2.259 4.016 5.334 4.184 6.589 3.765.209 4.079.69 12.55.941 13.805-12.863.942-15.687-5.02-17.883-8.471-2.761 3.765-.314 10.563 1.255 13.491-8.472-.209-25.791-.69-27.297-.941-.627-6.275 4.393-18.825 5.648-21.649-3.263-.251-6.171 2.406-7.216 3.765Z"
fill="#011650"
/>
<path
d="M296.321 145.266h4.392"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M298.203 165.66c-3.012 4.016-7.321 6.275-9.099 6.902l-.627-12.864c.941-.418 3.137-1.757 4.392-3.765 1.569-2.51 1.883-5.647 4.393-8.785 2.51-3.137 7.216-4.392 9.413-4.392 2.196 0 3.765.941 3.765 1.882 0 .753-1.255.523-1.883.314.209.105.628.565.628 1.569 0 1.255-.314 1.882-.628 2.196.314.628.628.941.314 2.196-.314 1.255-1.569 2.51-2.824 2.51s-2.51 2.197-3.451 3.765c-.941 1.569-.628 3.452-4.393 8.472ZM231.688 164.091c-2.761-8.534.523-13.387 2.51-14.746 2.51 2.614 7.781 8.094 8.785 9.098-1.882 3.765 1.255 6.903 2.824 6.903 1.255 0 2.196-1.046 2.51-1.569.209-.418.941-1.38 2.196-1.882 1.569-.628 2.51 0 2.824.313.251.251.314.942.314 1.255.209-.104.753-.125 1.255.628.502.753.418 1.36.313 1.569.105.104.314.376.314.627.314.628-.314 1.569-.627 1.883-.314.313-.942.941-1.255 1.568-.314.628-3.138 2.824-5.02 4.707-1.506 1.506-3.138 1.673-3.765 1.568-3.243-.418-10.417-3.388-13.178-11.922Z"
fill="#FC466F"
/>
<path
d="M252.709 163.463c-1.15.105-3.514.816-3.765 2.824M254.278 165.659c-.837-.104-2.698.44-3.451 3.452"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M275.3 150.6c0 3.451 0 7.843-3.765 12.55"
stroke="#fff"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M304.165 143.697c.209.941-.063 2.886-2.824 3.137.942.942 1.255 3.138 0 4.393M307.302 145.893c-1.045.418-3.012 1.632-2.51 3.138.314.941 2.197.313 2.824 0-1.568.941-2.824 1.882-2.196 3.451"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M305.733 145.266h7.53"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
stroke="#F6BCD1"
stroke-width="1.255"
stroke-linecap="round"
stroke-linejoin="round"
d="M23.671 80.319h96.008M41.87 82.829l14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.789-14.432 11.788 14.432 11.788-14.432 11.788"
/>
<path
d="m56.616 50.826.372-.505a.628.628 0 0 0-1 .505h.628Zm.627 198.291V50.827h-1.255v198.29h1.255Zm-1-197.786 39.533 29.18.745-1.01-39.533-29.18-.745 1.01Z"
fill="#F6BCD1"
/>
<path
d="m41.242 80.633 15.373-13.805M41.242 249.117V80.947M122.146 83.853l10.668 26.983"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<path
d="M50.804 249.118H25.569c-5.933-10.272-14.823-31.372-2.922-33.603 14.875-2.789 18.727 15.938 18.727 18.329 7.65-2.444 9.474 9.164 9.43 15.274Z"
fill="#E2E5EC"
/>
<path
d="M26.694 223.351c1.816 2.922 6.012 11.124 10.156 24.512M44.226 237.828l-3.32 6.907"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<circle
cx="121.249"
cy="81.888"
r="2.824"
fill="#fff"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<circle
cx="133.798"
cy="112.008"
r="2.196"
fill="#fff"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<circle opacity=".3" cx="374.76" cy="123.303" r="5.334" fill="#8BD9FC" />
<defs>
<linearGradient
id="a"
x1="202.51"
y1="119.301"
x2="202.51"
y2="251"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F7F4FD" />
<stop offset="1" stop-color="#F7F4FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1="84.54"
y1="187.309"
x2="102.424"
y2="158.444"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1="184.312"
y1="106.047"
x2="180.707"
y2="172.767"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#ABE7FF" />
<stop offset="1" stop-color="#ADE4FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1="298.204"
y1="247.235"
x2="305.574"
y2="193.577"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1="205.334"
y1="247.235"
x2="212.704"
y2="193.577"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="g"
x1="142.583"
y1="96.948"
x2="119.437"
y2="93.029"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#C9D7FF" />
<stop offset="1" stop-color="#C9D7FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="h"
x1="108.697"
y1="176.472"
x2="131.959"
y2="148.997"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="i"
x1="66.342"
y1="212.095"
x2="103.79"
y2="225.01"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A6E6D3" stop-opacity=".4" />
<stop offset="1" stop-color="#A6E6D3" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
<el-button type="primary" @click="backUrl">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const backUrl = () => {
router.push({
name: 'home',
query: {
// ...route.query,
},
})
}
</script>
+53
View File
@@ -0,0 +1,53 @@
<template>
<el-card class="box-card col-center" shadow="never">
<div class="box-card-title">图片上传 🍱🍱🍱🍱🍱</div>
<el-upload
v-model:file-list="fileList"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-image-viewer v-if="dialogVisible" @close="imageView" style="width: 100px; height: 100px" :url-list="[dialogImageUrl]" />
</el-card>
</template>
<script setup>
import { reactive, ref } from "vue";
import { Plus } from "@element-plus/icons-vue";
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const pictureUpload = ref(null);
const fileList = ref([]);
const handleRemove = (file) => {
console.log(file);
};
const imageView = () => {
dialogVisible.value = false;
};
const handlePictureCardPreview = (file) => {
console.log(file.url);
dialogImageUrl.value = file.url;
dialogVisible.value = true;
};
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
padding-bottom: 20px;
}
}
.el-image-viewer__canvas {
img {
width: 120px !important;
height: auto !important;
}
}
</style>
+75
View File
@@ -0,0 +1,75 @@
<template>
<el-card class="box-card col-center" shadow="never">
<div class="box-card-title">数字动画 🍝🍝🍝🍝</div>
<div class="number-grow-warp">
<span ref="numberGrow" :data-time="time" class="number-grow" :data-value="number">0</span>
</div>
</el-card>
</template>
<script setup>
/* 需求说明:
1.数字不需要千位符,但是为了防止以后要有 所以加了个参数判断,默认是没有的
2.数字整数变动
3.组件改为行内元素,能更好的兼容页面样式
4.第二次数字变动在上次的数字累加
5.添加监听器防止页面不更新的情况
*/
import { onMounted, reactive, ref, watch } from "vue";
const time = reactive(6000);
const thousandSign = ref(false);
const number = ref(997052786868686);
const oldValue = ref(6868686);
const numberGrow = ref(null);
const setNumberGrow = (ele) => {
let value = number.value - oldValue.value;
let step = (value * 10) / (time * 100);
let current = 0;
let start = oldValue.value;
let t = setInterval(function () {
start += step;
if (start > number.value) {
clearInterval(t);
start = number.value;
t = null;
}
if (current === start) {
return;
}
current = parseInt(start);
oldValue.value = current;
if (thousandSign.value) {
ele.innerHTML = current.toString().replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, "$1,");
} else {
ele.innerHTML = current.toString();
}
}, 10);
};
watch(number.value, (newV, oldV) => {
setNumberGrow(numberGrow.value);
// gsap.to(this, { duration: 0.5, tweened: Number(n) || 0 });
});
// watch: {
// number(n) {
// gsap.to(this, { duration: 0.5, tweened: Number(n) || 0 })
// }
// }
onMounted(() => {
setNumberGrow(numberGrow.value);
});
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
margin-bottom: 20px;
}
}
.number-grow-warp {
transform: translateZ(0);
}
</style>
+20
View File
@@ -0,0 +1,20 @@
<template>
<div class="content-box" style="height: 100%">
<iframe src="https://cn.bing.com/" frameborder="0" class="full-iframe"></iframe>
</div>
</template>
<style lang="scss" scoped>
.content-box {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
background-color: #fff;
}
.full-iframe {
// margin-top: 2.5%;
width: 100%;
height: 100%;
}
</style>
+87
View File
@@ -0,0 +1,87 @@
<template>
<el-card class="box-card" shadow="never">
<div class="box-card-title">文件上传 🍱🍱🍱🍱🍱</div>
<el-upload
class="upload-demo"
drag
multiple
:show-file-list="true"
:limit="excelLimit"
:http-request="uploadExcel"
:before-upload="beforeExcelUpload"
:on-exceed="handleExceed"
:on-success="excelUploadSuccess"
:on-error="excelUploadError"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>或点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
</template>
</el-upload>
</el-card>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElNotification } from 'element-plus'
// 最大文件数
const excelLimit = ref(1)
// 文件上传
const uploadExcel = (param) => {}
const beforeExcelUpload = (file) => {
const isExcel =
file.type === 'application/vnd.ms-excel' ||
file.type ===
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isExcel)
ElNotification({
message: '上传文件只能是 xls / xlsx 格式!',
type: 'warning',
})
if (!isLt5M)
ElNotification({
message: '上传文件大小不能超过 5MB',
type: 'warning',
})
return isExcel && isLt5M
}
// 文件数超出提示
const handleExceed = () => {
ElNotification({
message: '最多只能上传一个文件!',
type: 'warning',
})
}
// 上传错误提示
const excelUploadError = () => {
ElNotification({
message: '导入数据失败,请您重新上传!',
type: 'error',
})
}
// 上传成功提示
const excelUploadSuccess = () => {
ElNotification({
message: '导入数据成功!',
type: 'success',
})
}
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
padding-bottom: 20px;
}
}
.upload-demo {
width: 50%;
}
</style>
+61
View File
@@ -0,0 +1,61 @@
<template>
<el-card class="box-card col-center" shadow="never">
<div class="box-card-title">引导页 🍮🍯🍳🍔</div>
<el-button type="primary" @click.prevent.stop="guide">开启指引</el-button>
</el-card>
</template>
<script setup>
import Driver from "driver.js";
import "driver.js/dist/driver.min.css";
const guide = () => {
const driver = new Driver({
allowClose: false,
doneBtnText: "结束",
closeBtnText: "关闭",
nextBtnText: "下一步",
prevBtnText: "上一步",
});
driver.defineSteps(steps);
driver.start();
};
const steps = [
{
element: "#collapseIcon",
popover: {
title: "Collapse Icon",
description: "Open && Close sidebar",
position: "right",
},
},
{
element: "#breadcrumb",
popover: {
title: "Breadcrumb",
description: "Indicate the current page location",
position: "right",
},
},
{
element: "#fullscreen",
popover: {
title: "Full Screen",
description: "Full Screen, Exit The Full Screen Page",
position: "left",
},
},
];
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
margin-bottom: 20px;
}
}
</style>
+45
View File
@@ -0,0 +1,45 @@
const setWaterCanvas = (str1, color) => {
console.log(color);
const id = "1.23452384164.123412415";
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
const can = document.createElement("canvas");
can.width = 120;
can.height = 80;
const cans = can.getContext("2d");
cans.rotate((-20 * Math.PI) / 180); // 水印旋转角度
cans.font = "15px Vedana";
cans.fillStyle = color;
cans.textAlign = "center";
cans.textBaseline = "Middle";
cans.fillText(str1, can.width / 2, can.height);
const div = document.createElement("div");
div.id = id;
div.style.pointerEvents = "none";
div.style.top = "40px";
div.style.left = "0px";
div.style.opacity = "0.15";
div.style.position = "fixed";
div.style.zIndex = "100000";
div.style.width = document.documentElement.clientWidth + "px";
div.style.height = document.documentElement.clientHeight + "px";
div.style.background = "url(" + can.toDataURL("image/png") + ") left top repeat";
document.body.appendChild(div);
return id;
};
// 创建水印
export function setWatermark(str1, color) {
let id = setWaterCanvas(str1, color);
if (document.getElementById(id) === null) {
id = setWaterCanvas(str1, color);
}
}
// 清除水印
export function clear() {
const id = "1.23452384164.123412415";
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
}
+24
View File
@@ -0,0 +1,24 @@
<template>
<el-card>
<div class="card-title">markdown编辑器🍬🍬🍬🍭🍭🍭<span @click="gotoUrl">[文档链接]</span></div>
<v-md-editor model="text" height="80vh"></v-md-editor>
</el-card>
</template>
<script setup>
import { ref } from "vue";
const text = ref("");
const gotoUrl = () => {
window.open("https://ckang1229.gitee.io/vue-markdown-editor/zh/#%E4%BB%8B%E7%BB%8D", "_blank");
};
</script>
<style lang="scss" scoped>
.card-title {
padding-bottom: 10px;
font-size: 18px;
font-weight: bold;
span {
color: #673ab7;
cursor: pointer;
}
}
</style>
+84
View File
@@ -0,0 +1,84 @@
<template>
<el-card :shadow="never" class="col-center">
<div class="p10">密码强度🍓🍇🍈🍉</div>
<el-form
label-position="left"
label-width="100px"
:model="formLabelAlign"
style="width: 460px"
>
<el-form-item label="请输入密码">
<el-input
v-model="formLabelAlign.passsword"
:prefix-icon="Lock"
type="password"
show-password
/>
</el-form-item>
<el-form-item label="密码强度">
<el-progress
v-show="formLabelAlign.passsword"
:percentage="percentage"
style="width: 460px"
:status="status"
/>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
const formLabelAlign = reactive({
passsword: '',
})
let percentage = ref(0)
let status = ref('exception')
const checkStrong = (sValue) => {
var modes = 0
if (sValue.length < 1) return modes
if (/\d/.test(sValue)) modes++ //数字
if (/[a-z]/.test(sValue)) modes++ //小写
if (/[A-Z]/.test(sValue)) modes++ //大写
if (/\W/.test(sValue)) modes++ //特殊字符
switch (modes) {
case 1:
return 1
break
case 2:
return 2
break
case 3:
case 4:
return sValue.length < 10 ? 3 : 4
break
}
return modes
}
const statusChange = (modes) => {
if (modes == 1) {
percentage.value = 25
status.value = 'exception'
} else if (modes == 2) {
percentage.value = 50
status.value = 'exception'
} else if (modes == 3) {
percentage.value = 75
status.value = 'warning'
} else {
percentage.value = 100
status.value = 'success'
}
}
watch(
() => formLabelAlign.passsword,
(newValue, oldValue) => {
let modes = checkStrong(newValue)
console.log(modes)
statusChange(modes)
}
)
</script>
+278
View File
@@ -0,0 +1,278 @@
<template>
<el-card class="clo-center">
<div class="p10">验证组件🍨🍨🍨🍧🍧🍧</div>
<el-form label-position="left" inline label-width="100px" :model="formLabelAlign">
<el-form-item label="请输入验证码" style="margin-right: 0">
<el-input v-model="formLabelAlign.code" />
</el-form-item>
<el-form-item>
<div class="s-canvas">
<canvas @click="upDataCode" ref="canvasRef" :width="contentWidth" :height="contentHeight"></canvas></div
></el-form-item>
</el-form>
<div className="sliderContent">
<div className="imgDev" style="width: 500px">
<canvas id="canvasImg" width="500" height="auto"></canvas>
<canvas className="slider" id="sliderBlock" :width="500" :height="280" :style="{ left: sildLeft + 'px' }"></canvas>
<!-- <el-slider class="moveSlider" @change="changeValue1" v-model="value1" /> -->
</div>
</div>
<div class="moveSlider" :style="{ background: verifyResult ? '#859baa' : '#f7f8fa', color: verifyResult ? '#fff' : '606266' }">
{{ verifyResult ? "解锁成功" : "滑动解锁" }}
<div class="slider" @mousedown="sliderMove" ref="slider">
<el-icon v-if="verifyResult" class="icon-style" style="color: #859baa"><Select /></el-icon>
<el-icon v-else class="icon-style"><DArrowRight /></el-icon>
</div>
</div>
</el-card>
</template>
<script setup>
import { DArrowRight, Select } from "@element-plus/icons-vue";
import { onMounted, reactive, ref } from "vue";
const contentWidth = ref(100);
const contentHeight = ref(32);
const sliderWidth = ref();
const sliderHeight = ref();
const sildLeft = ref(0);
const value1 = ref(0);
const sildY = ref();
const sildX = ref();
const verifyResult = ref(false);
const formLabelAlign = reactive({
code: "",
});
const identifyCodes = ref("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
const identifyCode = ref("1234");
const canvasRef = ref(null);
// 生成随机数
const randomNum = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
const randomColor = (min, max) => {
var r = randomNum(min, max);
var g = randomNum(min, max);
var b = randomNum(min, max);
return "rgb(" + r + "," + g + "," + b + ")";
};
const drawPic = () => {
let ctx = canvasRef.value.getContext("2d");
ctx.fillStyle = randomColor(180, 230); //填充颜色
ctx.fillRect(0, 0, contentWidth.value, contentHeight.value); //填充位置
// drawLine(ctx);
let imgCode = "";
// 随机产生字符串,并且随机旋转
for (let i = 0; i < 4; i++) {
const text = identifyCodes.value[randomNum(0, identifyCodes.value.length)];
const deg = randomNum(-30, 30);
imgCode += text;
ctx.font = randomNum(18, 40) + "px Simhei"; //填充字体
ctx.textBaseline = "top";
ctx.fillStyle = randomColor(80, 150);
drawDot(ctx);
ctx.save(); //保存
ctx.translate(30 * i + 15, 15);
ctx.rotate((deg * Math.PI) / 180); //随机的旋转
ctx.fillText(text, -15 + 5, -15); //
ctx.restore(); //清除
}
drawLine(ctx);
return imgCode;
};
const drawLine = (ctx) => {
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = randomColor(0, 255);
ctx.beginPath();
ctx.moveTo(randomNum(0, contentWidth.value), randomNum(0, contentHeight.value));
ctx.lineTo(randomNum(0, contentWidth.value), randomNum(0, contentHeight.value));
ctx.stroke();
}
};
const drawDot = (ctx) => {
for (let i = 0; i < 20; i++) {
ctx.fillStyle = randomColor(0, 255);
ctx.beginPath();
ctx.arc(randomNum(0, contentWidth.value), randomNum(0, contentHeight.value), 1, 0, 2 * Math.PI);
ctx.fill();
}
};
onMounted(() => {
drawPic();
loadImage();
});
const upDataCode = () => {
identifyCode.value = "";
makeCode(identifyCodes.value, 4);
};
const makeCode = (val, l) => {
for (let i = 1; i < val.length && i <= l; i++) {
identifyCode.value += identifyCodes.value[Math.floor(Math.random() * (identifyCodes.value.length - 0) + 0)];
}
console.log(identifyCode.value);
drawPic();
};
// 拼图
const loadImage = () => {
//加载图片
let mainDom = document.getElementById("canvasImg");
let bg = mainDom.getContext("2d");
let imgSrc =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202001%2F30%2F20200130003214_3GJWF.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1663755123&t=0f35d18b29de642d5af549a5ec7062e5";
let img = document.createElement("img");
img.src = imgSrc;
img.onload = () => {
mainDom.height = (img.height / img.width) * mainDom.width;
bg.drawImage(img, 0, 0, mainDom.width, mainDom.height);
let r = 10; //半圆的半径
let w = 60; //滑块的宽度
let x = randomNum(85, mainDom.width - w - r - 2); //保证缺口完全展示,不会隐藏
let y = randomNum(16, mainDom.height - w - r - 2);
sildY.value = y;
sildX.value = x;
bg.lineWidth = 1;
bg.strokeStyle = "white";
bg.beginPath();
bg.moveTo(x, y);
//top
bg.arc(x + w / 2, y, r, -Math.PI, 0, true);
bg.lineTo(x + w, y);
//right
bg.arc(x + w, y + w / 2, r, 1.5 * Math.PI, 0.5 * Math.PI, false);
bg.lineTo(x + w, y + w);
//bottom
bg.arc(x + w / 2, y + w, r, 0, Math.PI, false);
bg.lineTo(x, y + w);
//left
bg.arc(x, y + w / 2, r, 0.5 * Math.PI, 1.5 * Math.PI, true);
bg.lineTo(x, y);
bg.stroke();
bg.fillStyle = "rgba(0, 0, 0, 0.4)"; //设置背景颜色
bg.fill();
sliderContent(img);
};
};
const sliderContent = (img) => {
let r = 10; //半圆的半径
let w = 60; //滑块的宽度
let mainDom = document.getElementById("sliderBlock");
let ctx = mainDom.getContext("2d");
//index.store.ts
let x = sildLeft.value;
let y = sildY.value;
let PI = Math.PI;
ctx.lineWidth = 1;
//绘制
ctx.beginPath();
//left
ctx.moveTo(x, y);
//top
ctx.arc(x + w / 2, y, r, -PI, 0, true);
ctx.lineTo(x + w, y);
//right
ctx.arc(x + w, y + w / 2, r, 1.5 * PI, 0.5 * PI, false);
ctx.lineTo(x + w, y + w);
//bottom
ctx.arc(x + w / 2, y + w, r, 0, PI, false);
ctx.lineTo(x, y + w);
//left
ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true);
ctx.lineTo(x, y);
ctx.shadowBlur = 10;
ctx.shadowColor = "black";
ctx.stroke();
ctx.clip();
console.log(img);
ctx.drawImage(img, -sildX.value + sildLeft.value, 0, 500, 280);
// console.log(ctx)
};
const sliderMove = (event) => {
let disX = 0;
const iconWidth = 60;
const ele = document.querySelector(".moveSlider .slider");
const startX = event.clientX || event.touches[0].pageX;
const MaxX = 500 - iconWidth;
if (verifyResult.value) {
return false;
}
// 开始移动
const onMove = (e) => {
const endX = e.clientX || e.touches[0].pageX;
disX = endX - startX;
if (disX <= 0) {
disX = 0;
}
if (disX >= MaxX - iconWidth) {
disX = MaxX;
}
sildLeft.value = disX;
ele.style.transition = ".1s all";
ele.style.transform = `translateX(${disX}px)`;
console.log(ele.style.transform);
};
const onEnd = () => {
if (disX > sildX.value - 5 && disX < sildX.value + 5) {
verifyResult.value = true;
} else {
ele.style.transition = ".5s all";
ele.style.transform = "translateX(0)";
}
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onEnd);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onEnd);
};
</script>
<style lang="scss" scoped>
.s-canvas {
height: 38px;
}
.s-canvas canvas {
margin-top: 1px;
margin-left: 8px;
}
.sliderContent {
margin-top: 20px;
position: relative;
.slider {
position: absolute;
}
}
.moveSlider {
width: 500px;
height: 32px;
background-color: #f7f8fa;
font-size: 14px;
line-height: 32px;
text-align: center;
color: #606266;
position: relative;
border: 1px solid #e4e7ed;
.slider {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 32px;
background-color: #fff;
cursor: pointer;
.icon-style {
margin-top: 6px;
font-size: 20px;
text-align: center;
}
}
}
</style>
+66
View File
@@ -0,0 +1,66 @@
<template>
<el-card class="box-card col-center" shadow="never">
<span class="box-card-title">富文本编辑器 🍰🍰🍰🍩🍩🍩</span>
<div class="wangeditor-box">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
class="editor-txt"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</el-card>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref('<p>hello</p>')
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>富文本编辑器</p>'
}, 1500)
})
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...' }
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.wangeditor-box {
border: 1px solid #f0f2f5;
margin-top: 20px;
}
.editor-txt {
height: 75vh !important;
overflow-y: hidden;
text-align: left;
}
}
</style>
+49
View File
@@ -0,0 +1,49 @@
<template>
<el-row>
<el-col>
<el-card shadow="never">
<div class="p10">水印组件🍬🍬🍬🍭🍭🍭</div>
<el-form :inline="true" ref="formRef" :model="numberValidateForm" label-width="100px" class="demo-ruleForm">
<el-form-item
label="水印名称"
prop="text"
:rules="[
{ required: true, message: 'age is required' },
{ type: 'text', message: '请输入水印名称' },
]"
>
<el-input v-model="numberValidateForm.text" type="text" autocomplete="off" />
</el-form-item>
<el-form-item label="选择水印颜色" prop="color">
<el-color-picker v-model="numberValidateForm.color" show-alpha />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(formRef)">创建</el-button>
<el-button @click="resetForm(formRef)">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { reactive } from "vue";
import { setWatermark, clear } from "./index.js";
const numberValidateForm = reactive({
text: "",
color: "#4060c7",
});
const submitForm = () => {
console.log(numberValidateForm.color);
setWatermark(numberValidateForm.text, numberValidateForm.color);
};
const resetForm = () => {
numberValidateForm.text = "";
numberValidateForm.color = "";
clear();
};
</script>
+1
View File
@@ -0,0 +1 @@
<template>关于我</template>
+39
View File
@@ -0,0 +1,39 @@
<template>
<el-card>
<span class="box-card-title">拖拽🍻🍻🍻🍵🍵🍵</span>
<div class="box-card-content">
<div v-draggable class="drag-box flx-center text">我可以拖拽哦~</div>
<div
v-draggable
class="drag-box flx-center text"
style="background: #eba300; width: 200px; height: 200px"
>
我可以拖拽哦~
</div>
</div>
</el-card>
</template>
<script setup></script>
<style lang="scss" scoped>
.box-card-title {
padding: 10px;
// display: block;
}
.box-card-content {
width: 100%;
height: 80vh;
background-color: #fff;
position: relative;
.drag-box {
position: absolute;
top: 110px;
width: 300px;
height: 300px;
color: #fff;
background: #bad80a;
}
}
</style>
+17
View File
@@ -0,0 +1,17 @@
<template>
<el-card shadow="never">
<div>复制指令 🍤🍤🍤🍤🍤🍤</div>
<br />
<el-input v-model="input3" placeholder="复制指令 🍤🍤🍤🍤🍤" style="width: 100%">
<template #append>
<el-button :icon="CopyDocument" v-copy="input3">复制</el-button>
</template>
</el-input>
</el-card>
</template>
<script setup>
import { ref } from "vue";
import { CopyDocument } from "@element-plus/icons-vue";
const input3 = ref("复制内容复制内容复制内容复制内容复制内容");
</script>
+14
View File
@@ -0,0 +1,14 @@
<template>
<el-card shadow="never">
<div class="card-title">防抖指令 🍍🍓🍓🍓🍓</div>
<br />
<el-button type="primary" v-debounce="debounceClick">防抖按钮(1秒后执行)</el-button>
</el-card>
</template>
<script setup>
import { ElMessage } from "element-plus";
const debounceClick = () => {
ElMessage.success("我是防抖指令");
};
</script>
+14
View File
@@ -0,0 +1,14 @@
<template>
<el-card shadow="never">
<div class="card-title">长按指令 🍍🍓🍓🍓🍓</div>
<br />
<el-button type="primary" v-longPress="debounceClick">长按指令</el-button>
</el-card>
</template>
<script setup>
import { ElMessage } from "element-plus";
const debounceClick = () => {
ElMessage.success("我是长按指令");
};
</script>
+14
View File
@@ -0,0 +1,14 @@
<template>
<el-card shadow="never">
<div class="card-title">节流指令 🍍🍓🍓🍓🍓</div>
<br />
<el-button type="primary" v-throttle="throttleClick">节流指令</el-button>
</el-card>
</template>
<script setup>
import { ElMessage } from 'element-plus'
const throttleClick = () => {
ElMessage.success('我是节流指令')
}
</script>
+163
View File
@@ -0,0 +1,163 @@
<template>
<el-card style="height: 85vh">
<div id="histogram" style="width: 100%; height: 80vh"></div>
</el-card>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted } from 'vue'
const getHistorgram = () => {
document.getElementById('histogram').setAttribute('_echarts_instance_', '')
var chartDom = document.getElementById('histogram')
var myChart = echarts.init(chartDom)
let app = {}
var option
const categories = (function () {
let now = new Date()
let res = []
let len = 10
while (len--) {
res.unshift(now.toLocaleTimeString().replace(/^\D*/, ''))
now = new Date(+now - 2000)
}
return res
})()
const categories2 = (function () {
let res = []
let len = 10
while (len--) {
res.push(10 - len - 1)
}
return res
})()
const data = (function () {
let res = []
let len = 10
while (len--) {
res.push(Math.round(Math.random() * 1000))
}
return res
})()
const data2 = (function () {
let res = []
let len = 0
while (len < 10) {
res.push(+(Math.random() * 10 + 5).toFixed(1))
len++
}
return res
})()
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#283b56',
},
},
},
// legend: {},
// toolbox: {
// show: true,
// feature: {
// dataView: { readOnly: false },
// restore: {},
// saveAsImage: {},
// },
// },
dataZoom: {
show: false,
start: 0,
end: 100,
},
xAxis: [
{
type: 'category',
boundaryGap: true,
data: categories,
},
{
type: 'category',
boundaryGap: true,
data: categories2,
},
],
yAxis: [
{
type: 'value',
scale: true,
name: '价格',
max: 30,
min: 0,
boundaryGap: [0.2, 0.2],
},
{
type: 'value',
scale: true,
name: '订单数',
max: 1200,
min: 0,
boundaryGap: [0.2, 0.2],
},
],
series: [
{
name: 'Dynamic Bar',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: data,
},
{
name: 'Dynamic Line',
type: 'line',
data: data2,
},
],
}
app.count = 11
setInterval(function () {
let axisData = new Date().toLocaleTimeString().replace(/^\D*/, '')
data.shift()
data.push(Math.round(Math.random() * 1000))
data2.shift()
data2.push(+(Math.random() * 10 + 5).toFixed(1))
categories.shift()
categories.push(axisData)
categories2.shift()
categories2.push(app.count++)
myChart.setOption({
xAxis: [
{
data: categories,
},
{
data: categories2,
},
],
series: [
{
data: data,
},
{
data: data2,
},
],
})
}, 2100)
option && myChart.setOption(option)
window.onresize = () => {
myChart.resize()
}
}
onMounted(() => {
setTimeout(() => {
getHistorgram()
}, 1000)
})
</script>
+88
View File
@@ -0,0 +1,88 @@
<template>
<el-card style="height: 85vh">
<div id="line" style="width: 100%; height: 80vh"></div>
</el-card>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted } from 'vue'
const getHistorgram = () => {
document.getElementById('line').setAttribute('_echarts_instance_', '')
var chartDom = document.getElementById('line')
var myChart = echarts.init(chartDom)
let app = {}
var option = {
tooltip: {
trigger: 'axis',
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value} °C',
},
},
series: [
{
name: 'Highest',
type: 'line',
data: [10, 11, 13, 11, 12, 12, 9],
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' },
],
},
markLine: {
data: [{ type: 'average', name: 'Avg' }],
},
},
{
name: 'Lowest',
type: 'line',
data: [1, -2, 2, 5, 3, 2, 0],
markPoint: {
data: [{ name: '周最低', value: -2, xAxis: 1, yAxis: -1.5 }],
},
markLine: {
data: [
{ type: 'average', name: 'Avg' },
[
{
symbol: 'none',
x: '90%',
yAxis: 'max',
},
{
symbol: 'circle',
label: {
position: 'start',
formatter: 'Max',
},
type: 'max',
name: '最高点',
},
],
],
},
},
],
}
option && myChart.setOption(option)
window.onresize = () => {
myChart.resize()
}
}
onMounted(() => {
setTimeout(() => {
getHistorgram()
}, 1000)
})
</script>
+40
View File
@@ -0,0 +1,40 @@
<template>
<el-card>
<div id="container">111</div>
</el-card>
</template>
<script setup>
import { onMounted } from "vue";
const initMap = () => {
var map = new BMapGL.Map("container"); // 创建地图实例
var point = new BMapGL.Point(116.404, 39.915); // 创建点坐标
var marker = new BMapGL.Marker(point); // 创建标注
map.addOverlay(marker);
map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true);
var scaleCtrl = new BMapGL.ScaleControl(); // 添加比例尺控件
map.addControl(scaleCtrl);
var zoomCtrl = new BMapGL.ZoomControl(); // 添加缩放控件
map.addControl(zoomCtrl);
var cityCtrl = new BMapGL.CityListControl(); // 添加城市列表控件
map.addControl(cityCtrl);
map.addEventListener("click", function (e) {
// 拿到点击地图上这一点的经纬度
// alert(e.point.lng + ", " + e.point.lat);
// 让地图中心平滑移动到点击的点
map.panTo(new BMap.Point(e.point.lng, e.point.lat));
});
};
onMounted(() => {
// initMap();
});
</script>
<style lang="scss" scoped>
#container {
width: 100%;
height: 85vh;
}
</style>
+44
View File
@@ -0,0 +1,44 @@
<template>
<el-card>
<div id="gaodeMap">111</div>
</el-card>
</template>
<script setup>
import { nextTick, onMounted } from "vue";
const initGaoMap = () => {
var map = new AMap.Map("gaodeMap", {
zoom: 11, //级别
center: [116.397428, 39.90923], //中心点坐标
viewMode: "2D", //使用3D视图
});
// 在图面添加工具条控件,工具条控件集成了缩放、平移、定位等功能按钮在内的组合控件
map.addControl(new AMap.ToolBar());
// 在图面添加比例尺控件,展示地图在当前层级和纬度下的比例尺
map.addControl(new AMap.Scale());
// 在图面添加鹰眼控件,在地图右下角显示地图的缩略图
map.addControl(new AMap.HawkEye({ isOpen: true }));
// 在图面添加类别切换控件,实现默认图层与卫星图、实施交通图层之间切换的控制
// map.addControl(new AMap.MapType())
// 在图面添加定位控件,用来获取和展示用户主机所在的经纬度位置
// map.addControl(new AMap.Geolocation())
map.setFitView();
};
onMounted(() => {
nextTick(() => {
// initGaoMap()
});
});
</script>
<style lang="scss" scoped>
#gaodeMap {
width: 100%;
height: 85vh;
}
</style>
+70
View File
@@ -0,0 +1,70 @@
<template>
<el-card style="height: 85vh">
<div id="radar" style="width: 100%; height: 80vh"></div>
</el-card>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted } from 'vue'
const getHistorgram = () => {
document.getElementById('radar').setAttribute('_echarts_instance_', '')
var chartDom = document.getElementById('radar')
var myChart = echarts.init(chartDom)
let app = {}
var option = {
color: ['#33ccff ', '#ff99cc'],
radar: {
// shape: 'circle',
indicator: [
{ name: '体重', max: 6500 },
{ name: '打败', max: 16000 },
{ name: '完成目标', max: 30000 },
{ name: '身体年龄', max: 38000 },
{ name: '运动量', max: 52000 },
// { name: 'Marketing', max: 25000 },
],
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: 'Allocated Budget',
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: 'Actual Spending',
areaStyle: {
color: new echarts.graphic.RadialGradient(0.1, 0.6, 1, [
{
color: 'rgba(255, 145, 124, 0.1)',
offset: 0,
},
{
color: '#ff99cc ',
offset: 1,
},
]),
},
},
],
},
],
}
option && myChart.setOption(option)
window.onresize = () => {
myChart.resize()
}
}
onMounted(() => {
setTimeout(() => {
getHistorgram()
}, 1000)
})
</script>
+142
View File
@@ -0,0 +1,142 @@
<template>
<el-card>
<!-- <Row> 更多查看<a href="https://vueflow.dev/">Vue Flow官方文档</a> </Row> -->
<div class="mt-4">
<el-button type="primary" @click="resetTransform">重置</el-button>
<el-button type="success" @click="updatePos">修改属性</el-button>
<el-button type="success" @click="toggleclass">修改样式</el-button>
<el-button type="warning" @click="logToObject">查看属性</el-button>
</div>
<VueFlow v-model="elements" class="vue-flow-content">
<Background />
<MiniMap />
<Controls />
</VueFlow>
</el-card>
<el-dialog
v-model="dialogVisible"
title="流程图属性"
width="70%"
:before-close="handleClose"
>
<span>{{ dialogContent }}</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false"
>确认</el-button
>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import '@braks/vue-flow/dist/style.css'
import '@braks/vue-flow/dist/theme-default.css'
import {
Background,
Controls,
MiniMap,
VueFlow,
isNode,
useVueFlow,
} from '@braks/vue-flow'
import { ref } from 'vue'
// import { Message } from 'view-ui-plus'
const dialogVisible = ref(false)
const dialogContent = ref('')
const elementsDefault = [
{
id: '1',
type: 'input',
label: '自律',
position: { x: 250, y: 5 },
class: 'node-light',
},
{
id: '2',
label: '控玩玩手机的时间',
position: { x: 100, y: 100 },
class: 'node-light',
},
{
id: '3',
label: '养成良好生活习惯',
position: { x: 400, y: 100 },
class: 'node-light',
},
{
id: '4',
label: '培养阅读习惯',
position: { x: 400, y: 200 },
class: 'node-light',
},
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
]
const elements = ref(elementsDefault)
const {
onPaneReady,
onNodeDragStop,
onConnect,
addEdges,
setTransform,
toObject,
} = useVueFlow({
defaultZoom: 1.5,
minZoom: 0.2,
maxZoom: 4,
})
onPaneReady(({ fitView }) => {
fitView()
})
onNodeDragStop((e) => console.log('drag stop', e))
onConnect((params) => addEdges([params]))
const updatePos = () => {
elements.value.forEach((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 400,
y: Math.random() * 400,
}
}
})
}
const logToObject = () => {
dialogVisible.value = true
dialogContent.value = JSON.stringify(toObject())
// Message.info(JSON.stringify(toObject()))
}
const resetTransform = () => {
elements.value = elementsDefault
setTransform({ x: 0, y: 0, zoom: 1 })
}
const toggleclass = () =>
elements.value.forEach(
(el) => (el.class = el.class === 'node-light' ? 'node-dark' : 'node-light')
)
</script>
<style lang="scss" scoped>
.vue-flow-content {
height: 80vh;
.node-light {
background: none;
}
.node-dark {
background: #eeeeee;
}
}
.mt-4 {
float: right;
text-align: right;
margin-bottom: 20px;
}
</style>
+67
View File
@@ -0,0 +1,67 @@
<template>
<el-card>
<div class="card-title">添加input 🍯🍯🍯🍯🍯🍯</div>
<el-input v-model="input1" placeholder="请输入域名">
<template #prepend>Http://</template>
</el-input>
<el-input v-model="input1" placeholder="请输入..." class="mt10">
<template #prepend>
<el-select v-model="select" placeholder="" style="width: 115px">
<el-option label="邮箱" value="1" />
<el-option label="wexin" value="2" />
<el-option label="Tel" value="3" />
</el-select>
</template>
</el-input>
<el-input v-model="input1" placeholder="请输入..." class="mt10">
<template #append>.com</template>
</el-input>
<el-input v-model="input1" placeholder="请输入..." class="mt10">
<template #prepend>
<el-select v-model="select" placeholder="Select2" style="width: 115px">
<el-option label="分类一" value="1" />
<el-option label="分类二" value="2" />
<el-option label="分类三" value="3" /> </el-select
></template>
<template #append><el-button :icon="Search" /></template>
</el-input>
<el-input
class="mt10"
v-for="item in inputList"
:key="item"
:v-model="item"
placeholder="请输入"
>
<template #append
><el-button @click="deleteHanle(item)">Delete</el-button></template
>
</el-input>
<el-button icon="Plus" class="mt10 w100" @click="addInput()"
>添加</el-button
>
</el-card>
</template>
<script setup>
import { Search, Plus } from '@element-plus/icons-vue'
import { ref } from 'vue'
const input1 = ref('')
const inputList = ref([])
const select = ref('1')
const Select2 = ref('1')
const addInput = () => {
inputList.value.push('')
}
const deleteHanle = (item) => {
inputList.value.splice(inputList.value[item], 1)
}
</script>
<style lang="scss" scoped>
.card-title {
padding-bottom: 20px;
font-size: 18px;
text-align: center;
}
</style>
+79
View File
@@ -0,0 +1,79 @@
<template>
<el-card class="col-center">
<el-form :model="form" label-width="120px">
<el-form-item label="活动名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="区域一" value="shanghai" />
<el-option label="区域二" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="活动日期">
<el-col :span="11">
<el-date-picker
v-model="form.date1"
type="date"
placeholder="开始日期"
style="width: 100%"
/>
</el-col>
<el-col :span="2" class="开启活动">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker
v-model="form.date2"
placeholder="结束日期"
style="width: 100%"
/>
</el-col>
</el-form-item>
<el-form-item label="活动状态">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="活动类型">
<el-checkbox-group v-model="form.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="活动资源">
<el-radio-group v-model="form.resource">
<el-radio label="Sponsor" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="活动描述">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
// do not use same name with ref
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
console.log('submit!')
}
</script>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+109
View File
@@ -0,0 +1,109 @@
<template>
<el-card class="box-card" shadow="never"> 卡片列表 🍓🍇🍈🍉 </el-card>
<el-row
class="mt10"
style="height: 75vh; overflow: auto"
v-infinite-scroll="load"
>
<el-col :span="6" v-for="item in listData" :key="item" offset="1">
<el-card shadow="hover" class="card-lists">
<!-- <div
class="item-state-pos"
:style="{
backgroundColor:
item.pay_state == '1002'
? '#6C88D5'
: item.pay_state == '1003'
? '#fa4a1e'
: '#00AA5A',
}"
>
{{
item.pay_state == '1001'
? '已支付'
: item.pay_state == '1002'
? '待支付'
: '未支付'
}}
</div> -->
<div class="item-state-pos">
<el-icon><MoreFilled /></el-icon>
</div>
<div class="item-title">
<img src="../../assets/images/avart.jpg" alt="" />{{ item.title }}
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">发行日期</div>
<div class="cot-txt">{{ item.date }}</div>
</div>
<div class="cot">
<div class="cot-tit">客户姓名</div>
<div class="cot-txt">{{ item.user_name }}</div>
</div>
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">
金额 <span>{{ item.number }}</span>
</div>
</div>
<div class="cot">
<div class="cot-tit">
状态
<span
:class="[
item.pay_state == '1002'
? 'type suc-type'
: item.pay_state == '1003'
? 'type'
: 'type error-type',
]"
>{{
item.pay_state == '1001'
? '已支付'
: item.pay_state == '1002'
? '待支付'
: '未支付'
}}</span
>
</div>
</div>
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">详情</div>
<div class="cot-txt one-cut-txt">{{ item.content }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { cardlists } from '../../api/modules/index.js'
import { onMounted, ref } from 'vue'
const listData = ref([])
const load = () => {
cardlists().then((res) => {
listData.value = listData.value.concat(res.data.data)
})
// count.value += 2
}
const getListData = () => {
cardlists().then((res) => {
listData.value = res.data.data
})
}
onMounted(() => {
getListData()
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
+80
View File
@@ -0,0 +1,80 @@
<template>
<el-card class="box-card" shadow="never"> 基础列表 🍓🍇🍈🍉 </el-card>
<div v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
<div class="barlist flx-row" v-for="i in listData" :key="i">
<div class="item-img">
<img src="../../assets/images/avart.jpg" alt="" />
</div>
<div class="item-left">
<div class="name">{{ i.createUser }}</div>
<div class="subname">{{ i.email }}</div>
</div>
<div class="item-left">
<div class="name">{{ i.title }}</div>
<div class="subname">{{ i.createTime }}发票已发出</div>
</div>
<div class="item-right">{{ i.number }}</div>
<div
class="item-state flx-row"
:style="{
backgroundColor:
i.pay_state == '1002'
? '#6C88D5'
: i.pay_state == '1003'
? '#fa4a1e'
: '#00AA5A',
}"
>
<span>{{
i.pay_state == '1001'
? '已支付'
: i.pay_state == '1002'
? '待支付'
: '未支付'
}}</span>
</div>
</div>
<!-- <div v-for="i in listData" :key="i" class="infinite-list-item">
<div class="infinite-list-item-top">
{{ i.title }}
</div>
<div class="infinite-list-item-main flx-row">
<div style="flex: 1">创建人{{ i.createUser }}</div>
<div style="flex: 1">发布时间{{ i.createTime }}</div>
</div>
</div>-->
</div>
</template>
<script setup>
import { Newslist } from '../../api/modules/index.js'
import { onMounted, ref } from 'vue'
const listData = ref([])
const load = () => {
Newslist().then((res) => {
listData.value = listData.value.concat(res.data.data)
})
// count.value += 2
}
const getListData = () => {
Newslist().then((res) => {
listData.value = res.data.data
})
}
onMounted(() => {
getListData()
})
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
margin-bottom: 20px;
}
}
@import './index.scss';
</style>
+157
View File
@@ -0,0 +1,157 @@
.infinite-list {
height: 72vh;
padding: 0;
margin: 0;
list-style: none;
}
.infinite-list {
margin-top: 20px;
width: 100%;
height: 100%;
.barlist {
margin-bottom: 10px;
background-color: #fff;
padding: 10px;
border-radius: 2px;
.item-img {
margin-right: 10px;
img {
width: 34px;
height: 34px;
border-radius: 50%;
}
}
.item-left {
flex: 1;
display: flex;
flex-direction: column;
.name {
font-size: 16px;
}
.subname {
font-size: 14px;
color: rgba(83, 88, 117, 1);
}
}
.item-state {
margin-left: 20px;
span {
display: inline-block;
color: #fff;
font-size: 12px;
letter-spacing: 1px;
padding: 5px 10px;
border-radius: 6px;
// background-color: #1250FC;
}
}
}
}
.card-lists {
margin: 10px;
position: relative;
.item-state-pos {
position: absolute;
right: 0;
top: 10px;
// background-color: #1250FC;
color: #6C88D5;
font-size: 14px;
padding: 5px 10px;
border-radius: 0 0 0 20px;
}
.item-title {
img {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 5px;
vertical-align: text-top;
}
font-size: 16px;
font-weight: 600;
color: rgba(15, 18, 34, 1);
text-align: left;
vertical-align: top;
margin-bottom: 15px;
}
.item-content {
margin-bottom: 5px;
.cot {
flex: 1;
.cot-tit {
font-size: 14px;
font-weight: 400;
color: rgba(83, 88, 117, 1);
padding-bottom: 5px;
span {
font-size: 14px;
color: rgba(15, 18, 34, 1);
&.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
&.suc-type {
color: #48ac4c;
background: #e2ffef;
}
}
}
}
.cot-txt {
font-size: 14px;
font-weight: 400;
color: rgba(15, 18, 34, 1);
}
}
}
.item-bottom {
flex-flow: row-reverse;
}
}
.infinite-list .infinite-list-item+.list-item {
margin-top: 10px;
}
.mt-4 {
float: right;
text-align: right;
}
+259
View File
@@ -0,0 +1,259 @@
<template>
<el-card class="box-card" shadow="never">
<div class="flx-row">
搜索列表 🍓🍇🍈🍉
<el-input
type="text"
placeholder="请输入关键字..."
style="width: 300px; margin-left: 10px"
v-model="searchValue"
>
<template #append>
<el-button type="primary" :icon="Search" />
</template>
</el-input>
<div style="flex: 1"></div>
<div class="img" @click="checkList('1001')">
<svg
t="1663432921802"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4971"
data-spm-anchor-id="a313x.7781069.0.i30"
width="20"
height="20"
:fill="colorType == '1001' ? '#1250FC' : '#888888'"
>
<path
d="M849.1 128 174.9 128c-25.9 0-46.9 21-46.9 46.9l0 34.2c0 25.9 21 46.9 46.9 46.9l674.2 0c25.9 0 46.9-21 46.9-46.9l0-34.2C896 149 875 128 849.1 128z"
p-id="4972"
></path>
<path
d="M849.1 768 174.9 768c-25.9 0-46.9 21-46.9 46.9l0 34.2c0 25.9 21 46.9 46.9 46.9l674.2 0c25.9 0 46.9-21 46.9-46.9l0-34.2C896 789 875 768 849.1 768z"
p-id="4973"
></path>
<path
d="M849.1 448 174.9 448c-25.9 0-46.9 21-46.9 46.9l0 34.2c0 25.9 21 46.9 46.9 46.9l674.2 0c25.9 0 46.9-21 46.9-46.9l0-34.2C896 469 875 448 849.1 448z"
p-id="4974"
></path>
</svg>
</div>
<div class="img" @click="checkList('1002')">
<svg
t="1663433235072"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5148"
width="23"
height="23"
:fill="colorType == '1002' ? '#1250FC' : '#888888'"
>
<path
d="M433.1 480 174.9 480c-25.9 0-46.9-21-46.9-46.9L128 174.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C480 459 459 480 433.1 480z"
p-id="5149"
></path>
<path
d="M433.1 896 174.9 896c-25.9 0-46.9-21-46.9-46.9L128 590.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C480 875 459 896 433.1 896z"
p-id="5150"
></path>
<path
d="M849.1 480 590.9 480c-25.9 0-46.9-21-46.9-46.9L544 174.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C896 459 875 480 849.1 480z"
p-id="5151"
></path>
<path
d="M849.1 896 590.9 896c-25.9 0-46.9-21-46.9-46.9L544 590.9c0-25.9 21-46.9 46.9-46.9l258.2 0c25.9 0 46.9 21 46.9 46.9l0 258.2C896 875 875 896 849.1 896z"
p-id="5152"
></path>
</svg>
</div>
</div>
</el-card>
<div
v-infinite-scroll="load"
class="infinite-list"
style="overflow: auto"
v-if="colorType == 1001"
>
<div class="barlist flx-row" v-for="i in listData" :key="i">
<div class="item-img">
<img src="../../assets/images/avart.jpg" alt="" />
</div>
<div class="item-left">
<div class="name">{{ i.createUser }}</div>
<div class="subname">{{ i.email }}</div>
</div>
<div class="item-left">
<div class="name">{{ i.title }}</div>
<div class="subname">{{ i.createTime }}发票已发出</div>
</div>
<div class="item-right">{{ i.number }}</div>
<div
class="item-state flx-row"
:style="{
backgroundColor:
i.pay_state == '1002'
? '#6C88D5'
: i.pay_state == '1003'
? '#fa4a1e'
: '#00AA5A',
}"
>
<span>{{
i.pay_state == '1001'
? '已支付'
: i.pay_state == '1002'
? '待支付'
: '未支付'
}}</span>
</div>
</div>
</div>
<el-row
class="mt10"
style="height: 75vh; overflow: auto"
v-infinite-scroll="load"
v-if="colorType == 1002"
>
<el-col :span="6" v-for="item in cardlist" :key="item" offset="1">
<el-card shadow="hover" class="card-lists">
<!-- <div
class="item-state-pos"
:style="{
backgroundColor:
item.pay_state == '1002'
? '#6C88D5'
: item.pay_state == '1003'
? '#fa4a1e'
: '#00AA5A',
}"
>
{{
item.pay_state == '1001'
? '已支付'
: item.pay_state == '1002'
? '待支付'
: '未支付'
}}
</div> -->
<div class="item-state-pos">
<el-icon><MoreFilled /></el-icon>
</div>
<div class="item-title">
<img src="../../assets/images/avart.jpg" alt="" />{{ item.title }}
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">发行日期</div>
<div class="cot-txt">{{ item.date }}</div>
</div>
<div class="cot">
<div class="cot-tit">客户姓名</div>
<div class="cot-txt">{{ item.user_name }}</div>
</div>
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">
金额 <span>{{ item.number }}</span>
</div>
</div>
<div class="cot">
<div class="cot-tit">
状态
<span
:class="[
item.pay_state == '1002'
? 'type suc-type'
: item.pay_state == '1003'
? 'type'
: 'type error-type',
]"
>{{
item.pay_state == '1001'
? '已支付'
: item.pay_state == '1002'
? '待支付'
: '未支付'
}}</span
>
</div>
</div>
</div>
<div class="item-content flx-row">
<div class="cot">
<div class="cot-tit">详情</div>
<div class="cot-txt one-cut-txt">{{ item.content }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Newslist, cardlists } from '../../api/modules/index.js'
import { Search, Plus } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
const listData = ref([])
const cardlist = ref([])
const searchValue = ref('')
const colorType = ref('1001')
const load = () => {
if (colorType == '1002') {
cardlists().then((res) => {
cardlist.value = res.data.data
})
} else {
Newslist().then((res) => {
listData.value = listData.value.concat(res.data.data)
})
}
// count.value += 2
}
const getListData = () => {
Newslist().then((res) => {
listData.value = res.data.data
})
cardlists().then((res) => {
cardlist.value = res.data.data
})
}
const checkList = (type) => {
colorType.value = type
if (type == 1001) {
ElMessage({
message: '单列展示,切换成功!',
type: 'success',
})
} else {
ElMessage({
message: '多列展示,切换成功!',
type: 'success',
})
}
}
onMounted(() => {
getListData()
})
</script>
<style lang="scss" scoped>
.box-card {
width: 100%;
.box-card-title {
margin-bottom: 20px;
}
}
@import './index.scss';
</style>
+172
View File
@@ -0,0 +1,172 @@
<template>
<div id="main" style="width: 100%; height: 100%"></div>
</template>
<script setup name="GMVnearly">
import * as echarts from 'echarts'
import { onMounted } from 'vue'
const getEcharts = () => {
var chartDom = document.getElementById('main')
var myChart = echarts.init(chartDom)
const option = {
tooltip: {
//鼠标悬停时显示对应数据
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
title: {
text: '出入库情况',
top: '8px',
left: '10px',
bottom: '8px',
textStyle: {
color: '#191e24',
fontSize: '14',
},
},
legend: {
// 图例
data: ['出库', '入库'],
top: 8,
right: 16, // 修改位置
icon: 'circle', //原型
textStyle: {
color: '#191e2480', //字体颜色
},
},
grid: {
// 上下左右 边距
top: '8%',
left: '3%',
right: '3%',
bottom: '8%',
top: '15%',
containLabel: true,
},
xAxis: [
{
type: 'category',
axisTick: { show: false },
data: [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月',
],
axisLine: {
// 轴线的颜色以及宽度
lineStyle: {
color: '#A6A6A628',
},
},
axisLabel: {
// 轴文字的配置
show: true,
textStyle: {
color: '#191e2480',
},
},
splitLine: {
// 分割线配置
lineStyle: {
color: '#A6A6A628',
// type: "dashed", // 虚线
},
},
},
],
yAxis: [
{
min: 0, // 最小值
max: 500, //最大值
// splitNumber: 3, //划分3格
type: 'value',
axisLine: {
// 轴线的颜色以及宽度
show: false,
},
axisLabel: {
// 轴文字的配置
show: true,
// textStyle: {
// color: "#fff",
// },
},
splitLine: {
// 分割线配置
lineStyle: {
color: '#A6A6A628',
// type: "dashed",
},
},
},
],
series: [
{
name: '出库',
type: 'bar',
barWidth: 10, // 柱图宽度
barGap: '30%',
// label: labelOption,
emphasis: {
focus: 'series',
},
data: [320, 332, 401, 334, 390, 320, 332, 301, 334, 390, 320, 332],
// ↓ 这里可以改变渐变色的方向
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#6E48E6',
},
{
offset: 1,
color: '#3AA6FA ',
},
]),
},
{
name: '入库',
type: 'bar',
barWidth: 10, // 柱图宽度
// label: labelOption,
emphasis: {
focus: 'series',
},
data: [220, 182, 191, 234, 290, 220, 182, 191, 234, 290, 220, 182],
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: '#9DDD92',
},
{
offset: 1,
color: '#10BFAA',
},
]),
},
],
}
option && myChart.setOption(option)
window.onresize = () => {
myChart.resize()
}
}
onMounted(() => {
setTimeout(() => {
getEcharts()
}, 1000)
})
</script>

Some files were not shown because too many files have changed in this diff Show More