vue-admin-template-master
build
-----index.js webpack配置文件【很少修改这个文件】
mock
-------mock数据的文件夹【模拟一些假的数据mockjs实现的】,因为咱们实际开发的时候,利用的真是接口
node_modules
------项目依赖的模块
public
-------ico图标,静态页面,public文件夹里面经常放置一些静态资源,而且在项目打包的时候webpack不会编译这个文件夹
src
-------程序员代码的地方
-------api文件夹:涉及请求相关的
--------assets文件夹:里面放置一些静态资源(一般共享的),放在assets文件夹里面静态资源,在webpack打包的时候,会进行编译
---------components文件夹:一般放置非路由组件获取全局组件
---------icons:这个文件夹放置一些svg矢量图
---------layout文件夹,它里面放置一些组件与混入
---------router文件夹:与路由相关的
---------store文件夹:一定是与vuex相关的
---------style文件夹:与样式相关的
---------utils文件夹:request.js是axios二次封装文件
---------view文件夹:里面放置的是路由组件
App.vue 根组件
main.js 入口文件
permission.js 与导航守卫相关
settings:项目配置项文件
完成登录业务
1.静态组件完成
2.书写API(换成真实的接口)
3.axios的二次封装
4.换成真实接口之后需要解决代理跨域问题(这一步在vue.config.js里面操作)
4.1.首先在vue.config.js删掉请求mock的代码
4.2.在这里写入配置代理跨域
4.2.1在webpack中找到devserve中的proxy,找到以下代码
proxy: {
'/api': {
target: 'https://other-server.example.com',
secure: false,
},
},
根据vue-admin-template-master的.env.development可知base_api为'/dev-api'
退出登录业务
登陆之后所有的页面由layout构成,layout有三个主体区域Siderbar,Navbar,AppMain,
退出登录在navbar中,修改里面的文字内容
修改侧边栏
侧边栏的内容写在view中,根据业务需求存放数据,文字内容由meta元数据中的title修改,其中AppMain中的css在Style中的index.css中修改,那里面要把app-container改为app-main才能让padding有效果
修改trademark
trademark 由三部分 按钮 表格 分页器 构成
分页器的layout可以通过修改里面的顺序来改位置
layout=" prev, pager, next, jumper,->, sizes, total"
这里的->是靠右
trademark请求
请求列表数据
url: ’/admin/product/baseTrademark/{page}/{limit}‘ method:'get' 请求携带page和limit
:
通过拼接字符串
// 获取品牌列表
export function reqTrademarkPage(page, limit) {
return request({
url: `/admin/product/baseTrademark/${page}/${limit}`,
method: 'get',
})
}
async getPageList() {
const {page, limit} = this
let res = await this.$api.trademark.reqTrademarkPage(page, limit)
if (res.code == 200) {
this.total = res.data.total
this.records = res.data.records
}
}
这里不能使用params拼接,因为后台接口里面并没有携带占位符,如果使用了params就会使路径变成xxxx/xxx?xxx
没有设置需要传递params,所以对params参数没有进行处理,当我们传递params的时候,路径里会含有?id=,所以请求不到数据
所以在使用axios发起get请求的时候,需要根据后台接口设置来判断是否需要传递params
之后通过构建出一个表格
表格中logoUrl和操作的按钮需要通过template获取当前行的数据,也就是需要通过作用域插槽 (v-slot或者slot-scope)
当使用v-slot=“scpoe”的时候,获取里面的数据需要通过scope.row.属性
当使用slot-scope="(row,$index)"的时候,获取里面的数据需要通过row.属性
修改和添加按钮
修改按钮有两个功能 一、添加 二、修改
修改 url:/admin/acl/user/update method:put 请求携带三个参数 id,名称,logo
添加 url: '/admin/product/baseTrademark/save' method: 'post' 请求携带两个参数 名称,logo
当修改的时候需要获取id,当添加的时候不用获取id,因为添加之后id是从服务器中获取的
因此可以把修改和添加的请求合在一个,用if判断请求的是修改还是添加
// 修改品牌
export function reqTrademarkEdit(tradeMark) {
// 带给服务器数据携带ID 添加和修改集于一体
if (tradeMark.id) {
// 如果有id的话,修改
return request({
// 修改品牌列表 携带三个参数 id,名称,logo
url: '/admin/product/baseTrademark/update',
method: 'put',
data: tradeMark
})
} else {
return request({
// 添加品牌 携带两个参数 名称,logo
url: '/admin/product/baseTrademark/save',
method: 'post',
data: tradeMark
})
}
}
弄一个对话框,当用户点击添加或者修改按钮之后可以调用这个请求接口
<!-- 按钮 -->
<el-button type="primary" icon="el-icon-plus" style="margin-bottom: 20px;" @click="tradeAdd">添加</el-button>
<el-table-column prop="date" label="操作">
<template slot-scope="{row,$index}">
<el-button type="primary" icon="el-icon-edit" @click="tradeEdit(row)">修改</el-button>
<el-button type="danger" icon="el-icon-delete" @click="tradeDelete(row)">删除</el-button>
</template>
</el-table-column>
<!-- 对话框 -->
<el-dialog title="添加品牌" :visible.sync="editVisible" width="30%">
<el-form :model="editForm" :rules="editRules" ref="editForm">
<el-form-item label="品牌名称" label-width="120px" prop="tmName">
<el-input v-model="editForm.tmName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="品牌LOGO" label-width="120px">
<el-upload class="avatar-uploader" action="/dev-api/admin/product/fileUpload"
:show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="editForm.imageUrl" :src="editForm.imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editVisible = false">取 消</el-button>
<el-button type="primary" @click="AddOrUppdateClick">确 定</el-button>
</span>
</el-dialog>
el-upload上传
action需要修改自己提交的地址
methods: {
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
}
}
这上面的method是饿了么原生的方法
我们需要把上传成功之后的res的data传给data中存放的地方,再把el-load中的v-if和:src绑定给editForm-image
在表单验证的基础上调用接口
当点击添加的按钮时,我们需要传入商品名称和logo,这需要两个参数,而这两个参数属于editForm,当我们写入这两个数据之后,v-model已经双向绑定在了数据editForm中,调用接口时,只需要传入这个editForm
// 点击添加按钮
tradeAdd() {
this.editVisible = true
this.editForm.tmName = ''
this.editForm.logoUrl = ''
},
当点击修改的按钮时,我们需要知道当前的id,通过slot-scope得到当前行的数据,在点击之后传到editForm,但是这个需要深拷贝,如果是双向绑定的话,当我们在修改表单数据时,表格的数据也会一起发生变化,我们需要当用户点击完确定再响应页面
// 点击修改按钮
tradeEdit(row) {
this.editVisible = true
this.editForm = {...row}
},
然后如果成功的话通过是否有id来判断是修改还是品牌
// 点击添加按钮中确定
AddOrUppdateClick() {
this.$refs.editForm.validate(async valid => {
if (!valid) {
return false
}
this.editVisible = false
let res = await this.$api.trademark.reqTrademarkEdit(this.editForm)
console.log(res)
if (res.code == 200) {
this.$message.success(this.editForm.id ? '修改品牌成功' : '添加品牌成功')
this.getPageList()
} else {
this.$message.error('失败')
}
})
},
删除按钮
这个就直接贴代码了
// 删除品牌
export function reqTradeDelete(id){
return request({
url:`/admin/product/baseTrademark/remove/${id}`,
method:'delete'
})
}
<el-table-column prop="date" label="操作">
<template slot-scope="{row,$index}">
<el-button type="primary" icon="el-icon-edit" @click="tradeEdit(row)">修改</el-button>
<el-button type="danger" icon="el-icon-delete" @click="tradeDelete(row)">删除</el-button>
</template>
</el-table-column>
// 删除按钮
async tradeDelete(row) {
const confirmResult = await this.$confirm(`此操作将永久删除${row.tmName}, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(err => {
return err
})
if (confirmResult !== 'confirm') {
return this.$message.info('已取消删除')
}
let res = await this.$api.trademark.reqTradeDelete(row.id)
console.log(res)
if(res.code !== 200){
return false
}
this.$message.success('已删除成功')
this.getPageList(this.records.length>1?this.page:this.page-1)
}
attr请求
attr页面有可以共用的地方,于是我们创建一个全局组件
在components下新建一个CategorySelect文件,里面存放行内表单
这个组件全局使用,所以在main.js导入
import categoryselect from 'xxxx'
Vue.component(categoryselect.name,categoryselect)
attr里面有两个模块,一个是三级联动,一个是卡片
三级联动封装成CategorySelect
CategorySelect由一个行内表单构成,里面有一级分类、二级分类、三级分类,
1.先单向绑定表单数据cForm
2.每个el-select里面分别双向绑定一级分类id、二级分类id、三级分类id
3.挂载方法,直接调用此方法,获取一级分类数据,把数据存放在一个data数据中,然后在el-option中遍历数据得到选项
4.当选择一级分类数据之后,把选择的一级分类id传给二级分类,以此类推
5.当用户选择过选项,又想修改时,需要清空后面的选项,把data数据清空
6.CategorySelect这个组件会在attr里面用到,所以还需要子传父
<div>
<el-form :inline="true" class="demo-form-inline" :model="cForm">
<el-form-item label="一级分类" >
<el-select placeholder="请选择" v-model="cForm.category1Id" @change="changeCategory1">
<el-option v-for="(c1,index) in list1" :key="c1.id" :label="c1.name" :value="c1.id" ></el-option>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select placeholder="请选择" v-model="cForm.category2Id" @change="changeCategory2">
<el-option v-for="(c2,index) in list2" :key="c2.id" :label="c2.name" :value="c2.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select placeholder="请选择" v-model="cForm.category3Id" @change="changeCategory3">
<el-option v-for="(c3,index) in list3" :key="c3.id" :label="c3.name" :value="c3.id"></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
data () {
return {
// 一级分类的数据
list1:[],
// 二级分类的数据
list2:[],
// 三级分类的数据
list3:[],
// 收集相应的一级二级三级分类的id
cForm:{
category1Id:'',
category2Id:'',
category3Id:'',
}
}
},
mounted() {
// 获取一级分类的数据方法
this.getCategory1()
},
methods:{
// 一级分类
async getCategory1(){
let res = await this.$api.attr.reqGetCategory1()
if(res.code == 200){
this.list1 = res.data
}
},
// 改变了一级分类
async changeCategory1(){
// 清除数据
this.list2 = []
this.list3 = []
this.cForm.category2Id = ''
this.cForm.category3Id = ''
let res = await this.$api.attr.reqGetCategory2(this.cForm.category1Id)
if(res.code == 200){
this.list2 = res.data
}
},
// 改变了二级分类
async changeCategory2(){
// 清除数据
this.list3 = []
this.cForm.category3Id = ''
let res = await this.$api.attr.reqGetCategory3(this.cForm.category2Id)
if(res.code == 200){
this.list3 = res.data
}
},
// 三级分类有内容时
changeCategory3(){
if(this.cForm.category3Id){
this.$emit('getCategoryAll',this.cForm)
}
}
}
}
attr里面的卡片
先引用categoryselect,用事件接收三级联动的数据,并且存储在attr的data里面,用这个三级id的数据获取平台属性的数据
先写出静态,一个el-button,一个el-table。
el-button当点击之后,会切换到另外一个当前界面
el-table中的属性值列表和操作需要使用template,当选择修改的按钮时,也会切换到另外一个当前界面
另外一个界面:
先写出静态,用el-form写属性名
再用两个el-button写按钮
最后是一个表格
当没有输入属性名时,添加属性值的按钮要:disabled
el-table中数据需要在prop中写属性名,并且在el-table中单向绑:data="baseAttrInfo.attrValueList"需要在template中写el-input,而且这个数据要与row.valueName双向绑定,因此在改变input的时候,另外一个界面的数据也会发生改变,这是因为发生了浅拷贝,我们需要深拷贝来修改当前的数据,因为此时的数据格式为json,则不能使用{...row},{...row}只能用在对象中,json的格式并不适用,因为对象里面还包裹着数组,需要使用
// 深拷贝
this.baseAttrInfo = JSON.parse(JSON.stringify(row))
在点击修改的时候,在baseAttrInfo.attrValueList用$set新增一个属性
this.baseAttrInfo.attrValueList.forEach(item =>{
this.$set(item,'flag',false)
})
接下来我们需要在完成input和p的交换显示
点击p的时候,会显示input,input失焦的时候会变成p
因此在data中用flag控制显示与隐藏
input失焦变成p
当input输入的值不为空或者不能都是空格,而且不能与之前的属性值相同
我们需要判断
// 失去焦点的事件-----切换为查看模式,展示span
toLook(row) {
// 如果属性值为空不能作为新的属性值,需要用户提示,让他输入一个其他的属性值
if (row.valueName.trim() == '') {
this.$message.error('请输入属性值')
return false
}
// 新增的属性值不能与之前的属性值相同
let isReap = this.baseAttrInfo.attrValueList.some(item => {
// console.log(item)
// 需要将row从数组里面判断的时候去除
// row最新新增的属性值【数组的最后一项元素】
// 判断的时候,需要把已有的数组当中新增的这个属性值去掉
if (row !== item){
return row.valueName == item.valueName
}
})
if(isReap == true){
this.$message.error('属性值不能相同')
return false
}
row.flag = false
},
点击p的时候,会显示input,并且会自动聚焦到当前的input
我们需要获取input节点,实现自动聚焦,因为当切换dom的时候,对于浏览器来说,页面的结构发生了改变,页面需要重构
使用$nextTick,所指定的回调函数,会在DOM节点更新完毕之后再执行
<el-table style="width: 100%;margin-bottom: 20px;" border :data="baseAttrInfo.attrValueList">
<el-table-column type="index" label="序号" width="90px">
</el-table-column>
<el-table-column prop="valueName" label="属性值名称">
<template slot-scope="{row,$index}">
<el-input v-model="row.valueName" placeholder="请输入属性值名称" v-if="row.flag" @blur="toLook(row)"
@keyup.native.enter="toLook(row)" :ref="$index"></el-input>
<p class="clickspan" v-else @click="toForce(row,$index)">{{row.valueName}}</p>
</template>
</el-table-column>
<el-table-column label="操作" width="540px">
<template slot-scope="{row,$index}">
<el-button type="danger" icon="el-icon-delete" style="margin-bottom: 0;"
@click="deleteAttr(scope.row.attrId)"></el-button>
</template>
</el-table-column>
</el-table>
// 点击span,显示input
toForce(row,index){
row.flag = true
this.$nextTick(()=>{
// 获取input节点,实现自动聚焦
// 点击span的时候,切换为input,这对于浏览器来说,页面的结构变了,页面重绘与重排是需要耗时间
this.$refs[index].focus()
})
}
删除属性值
这里选用饿了么的Popconfirm气泡确认框,当选择删除的时候弹出气泡确认框选择是否删除
注意选用气泡确认框,el-button里面有个slot="reference"一定要写在里面
然后像之前一样发请求,然后重新调用数据就行了
<el-popconfirm :title="`你确定要删除${row.attrName}?`" @onConfirm="deleteAttr(row.id)">
<el-button slot="reference" type="danger" icon="el-icon-delete" style="margin-bottom: 0;"></el-button>
</el-popconfirm>
// 删除
async deleteAttr(id) {
let res = await this.$api.attr.reqDeleteAttr(id)
console.log(res)
// console.log(index)
this.getAttrList()
},
保存
点击了保存,用户填写的数据就会保存到数据库中
这里要把之前加入的属性flag删除
当属性名不为空,且attrValueList过滤出flag的属性并且删除,之后调用接口
async getSaveAttrInfo() {
this.baseAttrInfo.attrValueList = this.baseAttrInfo.attrValueList.filter(item => {
if (item.valueName.trim() !== '') {
delete item.flag
return item
}
})
try {
this.$message.success('添加成功')
let res = await this.$api.attr.reqSaveAttrInfo(this.baseAttrInfo)
this.getAttrList()
this.isShowTable = true
} catch (e) {
//TODO handle the exception
alert(e.message)
}
},
切换到界面二的时候,三级联动不能被选择
当切换到界面二,让上面的卡片:disabled="true",因为之前我们有控制切换界面一还是界面二的属性,我们用这个属性来控制卡片是否隐藏,界面一isShowTable=true,界面二isShowTable=false,并且这需要父传子
<el-card>
<categoryselect @getCategoryAll="getCategoryAll" :show="!isShowTable"></categoryselect>
</el-card>
categoryselect:
props:['show'],