Commit 51673b1b by 刘勇

初始化项目

parents
VITE_API_URL=http://192.168.1.105:8090
VITE_API_URL=http://192.168.1.106:8000
* text=auto eol=lf
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}
# vue-project
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>消息中心业务服务平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "vue-project",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.10.0",
"echarts": "^5.6.0",
"element-plus": "^2.10.4",
"pinia": "^3.0.3",
"vue": "^3.5.17",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.0",
"prettier": "3.5.3",
"vite": "^7.0.0",
"vite-plugin-vue-devtools": "^7.7.7"
}
}
<template>
<RouterView />
</template>
<script setup>
import { RouterView } from 'vue-router'
</script>
<style scoped>
</style>
import service from "@/utils/interceptors";
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
/* @import './base.css'; */
*{
margin: 0;
padding: 0;
}
\ No newline at end of file
<template>
<!-- 无子节点:渲染为 el-menu-item -->
<!-- <div>{{ menu }}</div> -->
<el-menu-item
v-if="!menu.children"
:index="menu.path"
>
<el-icon><Grid /></el-icon>
<span>{{ menu.meta.title }}</span>
</el-menu-item>
<!-- 有子节点:渲染为 el-sub-menu,再次递归 -->
<el-sub-menu v-else :index="menu.path">
<template #title>
<el-icon v-if="menu.meta.title == '工作台'"><Platform /></el-icon>
<el-icon v-else-if="menu.meta.title == '系统管理'"><Tools /></el-icon>
<el-icon v-else-if="menu.meta.title == '业务服务管理'"><HelpFilled /></el-icon>
<el-icon v-else-if="menu.meta.title == '消息管控'"><Promotion /></el-icon>
<el-icon v-else-if="menu.meta.title == '文件批量发送短信'"><Management /></el-icon>
<el-icon v-else-if="menu.meta.title == '故障处理'"><Briefcase /></el-icon>
<el-icon v-else-if="menu.meta.title == 'APP消息推送扩容'"><Comment /></el-icon>
<el-icon v-else-if="menu.meta.title == '消息内容安全校验'"><WarningFilled /></el-icon>
<el-icon v-else-if="menu.meta.title == '异网短信迁移'"><InfoFilled /></el-icon>
<el-icon v-else><Grid /></el-icon>
<span>{{ menu.meta.title }}</span>
</template>
<TreeNode
v-for="child in menu.children"
:key="child.id"
:menu="child"
/>
</el-sub-menu>
</template>
<script setup>
import { CaretBottom, Platform, QuestionFilled, Menu, InfoFilled, HelpFilled, WarningFilled, Briefcase, Promotion, Comment, Tools, Grid, BellFilled, Management } from '@element-plus/icons-vue'
import { reactive, ref, defineProps, defineEmits } from 'vue'
const props = defineProps({
menu: {
type: Object,
required: true
}
})
</script>
\ No newline at end of file
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { directives } from './utils/directives'
const app = createApp(App)
directives(app)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, { locale: zhCn })
app.mount('#app')
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import Login from '../views/login.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'App',
redirect: '/login'
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/home',
name: 'home',
component: () => import('../views/home.vue'),
children: [
// {
// path: '/toDo',
// name: 'toDo',
// component: () => import('../views/workBench/toDo.vue'),
// }
]
},
],
})
// 前置首位 访问权限控制
router.beforeEach((to) => {
})
// 后置守卫
router.afterEach((to) => {
})
export default router
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
import { getInfo } from "@/api/login";
export const directives = app => {
app.directive('debounce', {
// 自定义指令挂载时的处理逻辑
// vue3中的生命周期方法,el:当前指令绑定的元素,binding:当前指令对象,binding.value:指令绑定的值
// 当指令挂载到元素上时触发
mounted(el, binding){
// 判断指令绑定的值是否为函数,如果不是则直接返回
if(typeof binding.value !== 'function') return
// 初始化定时器为null
el.timer = null
// 定义事件处理函数
el.handler = function() {
// 如果定时器存在,则清除定时器
if(el.timer) {
clearTimeout(el.timer)
}
// 设置定时器,500毫秒后执行指令绑定的函数
el.timer = setTimeout(() => {
binding.value()
}, 500)
}
// 为元素添加点击事件监听器
el.addEventListener('click', el.handler)
},
// 自定义指令卸载前的处理逻辑
// 当指令从元素上卸载时触发
beforeUnmount(el, binding){
// 如果定时器存在,则清除定时器
if(el.timer){
el.timer = null;
clearTimeout(el.timer)
}
// 移除元素的点击事件监听器
el.removeEventListener('click', el.handler)
}
}),
app.directive('permission', {
mounted(el, binding, vnode) {
const permissions = JSON.parse(localStorage.getItem('permissions'));
console.log('权限值-----------')
console.log(permissions)
if(permissions.length == 1 && permissions[0]== '*') {//超级管理员['*']
return;
}
// 获取绑定的权限值
const data = binding.value;
if (!permissions.includes(data)) {
el.parentNode.removeChild(el);
}
}
})
}
\ No newline at end of file
// 下载文件通用函数
export function download(filename, data) {
let downloadUrl = window.URL.createObjectURL(data) //创建下载的链接
let anchor = document.createElement('a') // 通过a标签来下载
anchor.href = downloadUrl
anchor.download = filename // download属性决定下载文件名
anchor.click() // //点击下载
window.URL.revokeObjectURL(downloadUrl) // 下载后释放Blob对象
}
/*** 防抖函数 n 秒后再执行该事件,若在 n 秒内被重复触发,则重新计时
* @param fn 要被防抖的函数
* @param delay 规定的时间
*/
export function debounce(fn, delay) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
}
}
/*** 节流函数 n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
* @param fn 要被节流的函数
* @param wait 规定的时间
*/
export function throttled(fn, wait) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
}
}
}
\ No newline at end of file
import axios from 'axios';
import { ElMessage } from 'element-plus';
const service = axios.create({
baseURL: '/api',
timeout: 15000
})
// 请求拦截器
service.interceptors.request.use(config => {
return config;
}, error => {
return Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(response => {
if(response.data.code != '0000') {
ElMessage.error(response.data.msg);
}
return response.data;
}, error => {
// 处理一些错误信息
if (error.response) {
// 根据错误状态码进行处理
switch (error.response.status) {
case 401:
// 处理未授权的情况
ElMessage.error(error.response);
break;
case 403:
// 处理权限不足的情况
ElMessage.error(error.response);
break;
case 500:
// 处理服务器错误的情况
ElMessage.error(error.response);
break;
// ...
}
}
return Promise.reject(error)
})
export default service;
\ No newline at end of file
<template>
<div class="home">
<el-affix :offset="0">
<div class="header">
<div class="header_left">
<div class="logo"></div>
<div>消息中心业务服务平台</div>
</div>
<div class="header_right">
<el-badge is-dot>
<el-icon style="font-size: 25px;cursor: pointer;"><BellFilled color="#fff" /></el-icon>
</el-badge>
<el-icon style="font-size: 25px;cursor: pointer;"><QuestionFilled color="#fff" /></el-icon>
<el-dropdown>
<div class="dropdownBox">
<div class="avatar"></div>
<el-icon>
<CaretBottom color="#fff" />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="goLogin">退出系统</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span style="color: #fff;">{{ userName }}</span>
</div>
</div>
</el-affix>
<el-row style="height: calc(100% - 50px);">
<el-col :span="4">
<el-menu
:default-active="default_active"
:router="true"
style="height: 100%;"
class="side-menu"
>
<TreeNode
v-for="item in menuTree"
:key="item.id"
:menu="item"
/>
</el-menu>
</el-col>
<el-col :span="20" style="padding: 6px;">
<RouterView />
</el-col>
</el-row>
</div>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue';
import { CaretBottom, Platform, QuestionFilled, Menu, InfoFilled, HelpFilled, WarningFilled, Briefcase, Promotion, Comment, Tools, Grid, BellFilled, Management } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { RouterView, useRouter, useRoute } from 'vue-router';
import { logout, getRouters } from "@/api/login";
import TreeNode from '@/components/TreeNode.vue';
const router = useRouter();
const route = useRoute();
const default_active = ref('');
const menuTree = ref([]);
const userName = ref('');
onMounted(() => {
userName.value = JSON.parse(localStorage.getItem('userInfo')).loginName;
getMenu();
})
// 监听路由变化
watch(
() => route.path, // 监听 path 变化
(newPath, oldPath) => {
default_active.value = newPath.split('/')[1];
}
);
const goLogin = () => {
logout().then(res => {
if(res.code == '0000') {
ElMessage({
message: '退出成功!',
type: 'success',
})
localStorage.removeItem('userInfo')
router.push('/')
}
})
}
const getMenu = () => {
getRouters().then(res => {
if(res.code == '0000') {
const result = buildCleanTree(res.result);
menuTree.value = result;
default_active.value = route.name;
}
})
}
//剔除菜单数据中的按钮数据
const buildCleanTree = source => {
return source
.filter(node => Object.prototype.hasOwnProperty.call(node, 'path')) // 剔除无 path
.map(node => {
// 先递归处理子节点
const children = Array.isArray(node.children)
? buildCleanTree(node.children)
: undefined;
// 如果子节点为空数组,就不带 children;否则带上
return children?.length
? { ...node, children }
: (({ children: _, ...rest }) => rest)(node); // 去掉空 children
});
}
const handleOpen = (key, keyPath) => {
}
</script>
<style scoped>
.home{
height: 100vh;
}
.dropdownBox{
padding-top: 2px;
cursor: pointer;
display: flex;
align-items: flex-end;
}
.dropdownBox:focus-visible{
outline: none;
}
.avatar{
width: 22px;
height: 22px;
background-color: #fff;
border-radius: 22px;
margin-right: 2px;
}
.logo{
width: 30px;
height: 30px;
background-color: #fff;
border-radius: 30px;
margin-right: 10px;
}
.header{
height: 50px;
background-color: #409EFF;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
}
.header_left{
display: flex;
align-items: center;
color: #fff;
font-weight: bold;
}
.header_right{
display: flex;
align-items: flex-start;
width: 10%;
justify-content: space-between;
}
.el-menu--vertical {
--el-menu-item-height: 40px;
--el-menu-sub-item-height: 40px;
}
</style>
<template>
<div class="login">
<div class="login_box">
<el-form :model="loginForm">
<el-form-item>
<el-input v-model="loginForm.loginName" placeholder="用户名" />
</el-form-item>
<el-form-item>
<el-input v-model="loginForm.loginPwd" type="password" placeholder="密码" />
</el-form-item>
<div>
<el-button type="primary" @click="goHome" class="login_btn">登录</el-button>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { reactive, ref } from 'vue'
import { login, getInfo } from "@/api/login";
import { ElMessage } from 'element-plus'
const loginForm = reactive({
loginName: 'admin',
loginPwd: 'admin',
})
const router = useRouter();
const goHome = () => {
login(loginForm).then(res => {
if(res.code == '0000') {
ElMessage({
message: '登录成功!',
type: 'success',
})
getInfo().then(res2 => {
localStorage.setItem('permissions', JSON.stringify(res2.result.permissions))
localStorage.setItem('usersData', JSON.stringify(res2.result.user))
})
localStorage.setItem('userInfo', JSON.stringify(loginForm))
router.push('/home')
}
})
}
//解析路由参数
// 示例用法
// const url = 'https://example.com/page?name=John&age=25&interests=programming&interests=music';
// const parameters = getUrlParameters(url);
// console.log(parameters);
const getUrlParameters = url => {
const params = {};
const queryString = url.split('?')[1];
if (queryString) {
const paramPairs = queryString.split('&');
for (let i = 0; i < paramPairs.length; i++) {
const pair = paramPairs[i].split('=');
const key = decodeURIComponent(pair[0]);
const value = decodeURIComponent(pair[1] || '');
if (params[key]) {
if (Array.isArray(params[key])) {
params[key].push(value);
} else {
params[key] = [params[key], value];
}
} else {
params[key] = value;
}
}
}
return params;
}
</script>
<style scoped>
.login{
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-image: url(@/assets/login_bg.jpg);
background-repeat: no-repeat;
background-size: 100% 100%;
}
.el-form-item__label{
width: 60px;
}
:deep(.el-input__wrapper){
border-radius: 32px;
}
.login_box{
width: 300px;
height: 200px;
box-shadow: 0px 0px 3px 3px #fff;
padding: 20px;
border-radius: 3px;
}
.login_btn{
width: 100%;
border-radius: 32px;
margin-top: 40px;
}
</style>
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, './');
return {
server:{
cors: true,//开发模式
host: "0.0.0.0",
proxy: {
'^/api':{ /* 转发/api */
target: env.VITE_API_URL,
changeOrigin: true,//允许跨域
rewrite: path => path.replace(/^\/api/, "")//路径重新
}
}
},
plugins: [ vue(), vueDevTools() ],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
}
})
// export default defineConfig({
// server:{
// proxy: {
// '/api':{ /* 转发/api */
// target: 'http://192.168.1.103:8090/',
// changeOrigin: true,//允许跨域
// rewrite: path => path.replace(/^\/api/, "")//路径重新
// }
// }
// },
// plugins: [
// vue(),
// vueDevTools(),
// ],
// resolve: {
// alias: {
// '@': fileURLToPath(new URL('./src', import.meta.url))
// },
// },
// })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment