因为使用vite3,node版本需要 14.18+
git clone https://github.com/Thiefmooner/Src.git //把模板下载到本地
cd vue-manage-system //进入模板目录
npm install //安装项目依赖,等待安装完成之后,安装失败可用cnpm
npm run dev //运行
npm run build //执行构建命令,生成的dist文件夹放在服务器下即可访问
处理器:Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz 2.40 GHz
机带RAM:4.00 GB (3.89 GB 可用)
系统类型:64 位操作系统, 基于 x64 的处理器
Windows:Windows 10 专业版
处理器:Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz 2.40 GHz
机带RAM:4.00 GB (3.89 GB 可用)
系统类型:64 位操作系统, 基于 x64 的处理器
Windows:Windows 10 专业版
系统Vue版本:5.0.4
系统Node版本:16.15.0
package.json里Vue版本:3.2.37
pinia版本:2.0.20
vite版本:3.0.0
vue-manage-system-master
vue-manage-system-master
├─src
| ├─App.vue
| ├─main.ts
| ├─vite-env.d.ts
| ├─views
| | ├─403.vue
| | ├─404.vue
| | ├─charts.vue
| | ├─dashboard.vue
| | ├─donate.vue
| | ├─editor.vue
| | ├─export.vue
| | ├─fileSource.vue
| | ├─form.vue
| | ├─home.vue
| | ├─icon.vue
| | ├─import.vue
| | ├─login.vue
| | ├─markdown.vue
| | ├─permission.vue
| | ├─projectInfo.vue
| | ├─selectInfo.vue
| | ├─table.vue
| | ├─tabs.vue
| | ├─upload.vue
| | └user.vue
| ├─utils
| | ├─request.ts
| | └use-span-method.ts
| ├─store
| | ├─permiss.ts
| | ├─sidebar.ts
| | └tags.ts
| ├─router
| | └index.ts
| ├─components
| | ├─header.vue
| | ├─sidebar.vue
| | ├─sidebarOriginal.vue
| | └tags.vue
| ├─assets
| | ├─img
| | | ├─img.jpg
| | | ├─login-bg.jpg
| | | └Newu.png
| | ├─css
| | | ├─color-dark.css
| | | ├─icon.css
| | | └main.css
| ├─api
| | └index.ts
├─public
| ├─sidebar.json
| ├─table.json
| └template.xlsx
├─.vscode
| └launch.json
├─auto-imports.d.ts
├─components.d.ts
├─index.html
├─LICENSE
├─package-lock.json
├─package.json
├─README.md
├─result.txt
├─tsconfig.json
├─tsconfig.node.json
├─vite.config.ts
{
"id": "/",
"label": "基础信息",
"icon": "fa fa-send-o",
"hasChildren": true,
"items": [
{
"id": "/projectInfo",
"label": "项目信息明细",
"icon": "fa fa-location-arrow",
"hasChildren": false,
"items": [],
"parentId": "698f872c-407b-471b-a28b-eee69a4e64ba"
},
{
"id": "/fileSource",
"label": "文件库",
"icon": "fa fa-desktop",
"hasChildren": false,
"items": [],
"parentId": "698f872c-407b-471b-a28b-eee69a4e64ba"
},
{
"id": "/selectInfo",
"label": "文件搜索",
"icon": "fa fa-share-alt-square",
"hasChildren": false,
"items": [],
"parentId": "698f872c-407b-471b-a28b-eee69a4e64ba"
}
],
"parentId": "0"
}
{
"id":"string类型,”/想要到达的路径”(在此项目中同时被用作router属性的导航)"
"label":"string类型,选项名"
"icon": "string类型,图标,暂时用不到"
"hasChildren":"boolean类型,判断是否有子级元素"
"items":"数组类型,如果hasChildren为true,数组就有内容,内容为嵌套的同样结构体"
"parentId":"父元素的id,用来判断所属层级,同一级别的元素此属性值应该一样"
}
el-menu el-sub-menu el-menu-item分别为menu整体,一级菜单,二级菜单
el-menu标签要显式声明`router`属性,然后设置每个el-menu-item标签内的`index`属性,即可实现点击el-menu-item实现路由跳转
Warning:在此项目里,index属性同时为跳转path,所以点击后直接跳转到对应界面
<span>{{ item.label }}</span>外层要套一对`<template>`,才会正常渲染label值
v-if="item.hasChildren = = true"用来判断是否有子级菜单时,必须用= =
el-sub-menu el-menu-item绑定index属性时候,后面必须是字符串!!!在此,我拼接了一个空字符串`<el-sub-menu :index="item.id+''">`
有v-for的地方,就要有key值绑定!!主要用于dom diff算法,diff算法为同级比较,比较当前标签上的key还有他当前的标签名,如果key和标签名都一样时只移动,不会重新创建元素和删除元素
实现自动搜索功能,el-table上方放置一个搜索框input,里的v-model绑定search_Text数据项,实现双向绑定(内部数据和搜索框里的键入值实现同步)
然后在el-table绑定tables
这个计算属性
:data="tables"
动态和tables绑定
(计算属性是一种可以根据其他属性的值计算得出并且具有缓存功能的属性,它可以方便地对多个属性进行复杂的计算,并且在依赖的属性发生变化时自动更新)
computed:{
tables:function(){
var searchText=this.search_Text;
if(searchText){
return this.searchResult.filter(function(dataNews){
return Object.keys(dataNews).some(function(key){
return String(dataNews[key]).toLowerCase().indexOf(searchText) > -1
})
})
}
return this.searchResult//searchResult就是最后的筛选结果,演示时候把mock数据放在这里就可以
}
searchResult就是最后的筛选结果,演示时候把mock数据放在这里就可以
搜索框加了一个前置图标#prefix
<template #prefix>
<el-icon><search /></el-icon>
</template>
el-input搜索框v-model="inputInfo"
和内置数据双向绑定!
el-table绑定数据:data="projectInfo"
<el-table class="projectTable" :data="projectInfo">
el-table里自定义”合并同id单元格“方法sapnMethod()
在utils里封装一个工具类use-span-method
/**
* @param {Object} projectInfo
* @param {Object} rowSpanArray
*/
export function getRowSpanMethod(projectInfo:any, rowSpanArray:any) {
/**
* 要合并列的数据
*/
const rowSpanNumObject :any = {};
//初始化 rowSpanNumObject
rowSpanArray.map((item:any) => {
rowSpanNumObject[item] = new Array(projectInfo.length).fill(1, 0, 1).fill(0, 1);
rowSpanNumObject[`${item}-index`] = 0;
});
for (let i = 1; i < projectInfo.length; i++) {
rowSpanArray.map((key:any) => {
const index = rowSpanNumObject[`${key}-index`];
if (projectInfo[i][key] === projectInfo[i - 1][key]) {
rowSpanNumObject[key][index]++;
} else {
rowSpanNumObject[`${key}-index`] = i;
rowSpanNumObject[key][i] = 1;
}
});
}
const spanMethod = function({ row, column, rowIndex, columnIndex }:any) {
if (rowSpanArray.includes(column['property'])) {
const rowspan = rowSpanNumObject[column['property']][rowIndex];
if (rowspan > 0) {
return { rowspan: rowspan, colspan: 1 }
}
return { rowspan: 0, colspan: 0 }
}
return { rowspan: 1, colspan: 1 }
};
return spanMethod;
}
在projectInfo里导入,并且传入参数project_Id,说明project_Id相同时,会合并相同的单元格
import {getRowSpanMethod} from '../utils/use-span-method'
const spanMethod = getRowSpanMethod(projectInfo,['project_Id'])
😢
不过,这个projectInfo的实时搜索功能还没实现,逻辑太抽象了(自己写的太乱了)
按理说直接套用selectInfo的那个搜索框就可以,不过这个projectInfo里自己又封装了合并单元格功能,导致绑定的计算属性computed失效
{
project_Id: 'NEWUBK-2022140-022',
project_Name: 'Tom',
customer: 'No. 189, Grove St, Los Angeles',
is_Installed:'是',
is_Exported:'否',
design_Location:'天津',
process_Location:'天津',
company_Installation:'北京',
staff_Market:'jack',
staff_Machinery:'jack',
staff_Electrical:'jack',
date_Sign:'jack',
date_Send:'jack'
}
引入一个树结构,导航到各个文件的分类,比如电气专业文件
<el-tree :data="menuData" :props="defaultProps" @node-click="handleNodeClick" />
@node-click的函数handleNodeClick预留,点击节点后会触发的函数,用作请求后台数据用
<el-tree @node-click="handleNodeClick"></el-tree>
规定tree的interface接口,children参数可选(无,就是没有子级)
interface Tree {
label: string
children?: Tree[]
}
然后,实时搜索功能在这里得到了很好的复用,在selectInfo里封装的搜索组件在这应用上了
同样是el-input和el-table相互配合
el-table的数据项data在此很巧妙的绑定到计算属性fileArea
<el-table :data="fileArea">
fileArea计算属性computed和刚才如出一辙,是个动态变化的属性,随着搜索框的检索内容变化而变化
如果搜索内容为空,逻辑默认不检索
const fileArea = computed(()=>{
var searchArg = search
if(searchArg.value){
return tableData.filter(function(dataNews:any){
return Object.keys(dataNews).some(function(key){
return String(dataNews[key]).toLowerCase().indexOf(searchArg.value)> -1
})
})
}
return tableData
})
关于el-table-column实现下载功能
利用<template #default="{row}">
插槽功能,实现点击下载图标,下载文件功能
这个文件下载我想了很久怎么实现,后端http请求?后经过询问得知,有url,那就简单多了
其方法的本质是,外层遍历一圈icon图标,被放置在一个个的标签之中,这些标签指向一个个后端传回来的超链接
:href="row.url"
href指向row.url
<el-table-column prop="url" label="下载" >
<template #default="{row}">
<a :href="row.url" target="_blank" >
<el-icon><Download /></el-icon>
</a>
</template>
</el-table-column>
const service:AxiosInstance = axios.create({
timeout: 5000
});
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
return config;
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status === 200) {
return response;
} else {
Promise.reject();
}
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
export default service ;
<el-input v-model="param.username" >
@keyup.enter
意思为按下enter按键会触发后面的submitForm(login)
,login为参数
<el-input type="password"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
/**
* 这里暂时没对password做处理!!!都可以登录
*/
//先告诉你登录成功
ElMessage.success('登录成功');
//先把输入的用户名存到localStroage中的ms_username
localStorage.setItem('ms_username', param.username);
//如果是admin,就把admin的权限作为defaultList的参数;反之user的权限作为参数
//keys接受一个对应的权限列表
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
//重置用户权限
permiss.handleSet(keys);
//存储用户的权限列表到ms_keys
localStorage.setItem('ms_keys', JSON.stringify(keys));
router.push('/');
} else {
ElMessage.error('登录成功');
return false;
}
});
};
注意引入FormInstance, FormRules
import type { FormInstance, FormRules } from 'element-plus';
<el-form :model="param" :rules="rules" ref="login" label-width="0px">
const rules: FormRules = {
username: [
{
required: true,//required为true为必填,否则提醒输入
message: '请输入用户名',
trigger: 'blur'
}
],
password: [
{
required: true,//required为true为必填,否则提醒输入
message: '请输入密码',
trigger: 'blur'
}
]
};
export const usePermissStore = defineStore('permiss', {
state: () => {
const keys = localStorage.getItem('ms_keys');
return {
key: keys ? JSON.parse(keys) : <string[]>[],
defaultList: <ObjectList>{
admin: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],
user: ['1', '2', '3', '11', '13', '14', '15']
}
};
},
actions: {
handleSet(val: string[]) {
this.key = val;
}
}
});
在登录的时候,就会把用户名存储到ms_username,通过username参数再去permiss.ts里查询对应的权限数组defaultList
比如admin拥有所有权限['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],再通过permiss.handleSet()重置权限(有则覆盖,无则初始化)