menulist.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <template>
  2. <div class="mainBox">
  3. <!--搜索功能 start------------------------------------------------------------>
  4. <div class="layerBoxNoBg">
  5. <div>
  6. <el-button type="primary" @click="openWindow">添加菜单</el-button>
  7. </div>
  8. <div>
  9. </div>
  10. </div>
  11. <!--搜索功能 end------------------------------------------------------------>
  12. <!--表格内容 start------------------------------------------------------------>
  13. <div class="layerBox">
  14. <tableTitle :name="tableDivTitle"/>
  15. <el-row>
  16. <template>
  17. <!--设置default-expand-all将展开全部-->
  18. <el-table :data="tableData" style="width: 100%;margin-bottom: 20px;" row-key="id" border default-expand :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
  19. <el-table-column
  20. prop="id"
  21. label="id"
  22. sortable
  23. width="180">
  24. </el-table-column>
  25. <el-table-column
  26. prop="label"
  27. label="菜单名称"
  28. sortable
  29. width="180">
  30. </el-table-column>
  31. <el-table-column
  32. prop="url"
  33. label="路由地址">
  34. </el-table-column>
  35. <el-table-column fixed="right" label="操作" width="200" header-align="center">
  36. <template slot-scope="scope">
  37. <div class="listBtnBox">
  38. <div class="listDeleteBtn" @click="deleteRow(scope.row.id)"><i class="el-icon-delete"></i>移除</div>
  39. <div class="listEditBtn" @click="editRow(scope.row)"><i class="el-icon-edit-outline"></i>编辑</div>
  40. </div>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. </template>
  45. </el-row>
  46. </div>
  47. <!--表格内容 end------------------------------------------------------------>
  48. <!--弹出框 start------------------------------------------------------------>
  49. <el-dialog :title="editId ? '编辑菜单' : '添加菜单'" :visible.sync="windowStatus" :close-on-click-modal="false">
  50. <el-form :model="form" ref="form" :rules="formRules" label-position="left">
  51. <div class="formDiv">
  52. <el-form-item label="菜单名称" :label-width="formLabelWidth" prop="label" class="custom-align-right">
  53. <el-input v-model="form.label" autocomplete="off" placeholder="请输入菜单名称"></el-input>
  54. </el-form-item>
  55. <el-form-item label="是否为顶级菜单" :label-width="formLabelWidth" class="custom-align-right">
  56. <el-radio v-model="radio" label="1">是</el-radio>
  57. <el-radio v-model="radio" label="2">否</el-radio>
  58. </el-form-item>
  59. <el-form-item label="菜单路由" :label-width="formLabelWidth" prop="url" class="custom-align-right" v-if="radio === '2'">
  60. <el-input v-model="form.url" autocomplete="off" placeholder="请输入菜单路由"></el-input>
  61. </el-form-item>
  62. <el-form-item label="父级菜单名称" :label-width="formLabelWidth" prop="pid_arr" class="custom-align-right" v-if="radio === '2'">
  63. <el-cascader :key="parentKey" v-model="form.pid_arr" :props="{checkStrictly:true}" :options="pidArrData" clearable></el-cascader>
  64. </el-form-item>
  65. <el-form-item label="默认图标" :label-width="formLabelWidth" prop="icon" class="custom-align-right" v-if="radio == '1'">
  66. <!--图片上传组件 start ------------------------------------------------------------>
  67. <div class="uploaderBox">
  68. <div class="avatar-upload-container" @mouseenter="hovering = true" @mouseleave="hovering = false">
  69. <!-- 上传组件 -->
  70. <el-upload class="avatar-uploader" action="#" :show-file-list="false" :before-upload="beforeAvatarUpload">
  71. <!-- 预览图片 -->
  72. <img v-if="iconSrc" :src="iconSrc" class="avatar">
  73. <div v-else class="chooseImgDiv">
  74. <div>
  75. <img src="@/assets/public/upload/noImage.png">
  76. <div>选择图片</div>
  77. </div>
  78. </div>
  79. </el-upload>
  80. <!-- 删除按钮,当鼠标悬浮时显示 -->
  81. <div v-if="hovering && iconSrc" class="delete-button" @click="handleDelete">
  82. <i class="el-icon-delete"></i>
  83. </div>
  84. </div>
  85. </div>
  86. <!--图片上传组件 end ------------------------------------------------------------>
  87. </el-form-item>
  88. <el-form-item label="选中时图标" :label-width="formLabelWidth" prop="selected_icon" class="custom-align-right" v-if="radio == '1'">
  89. <!--图片上传组件 start ------------------------------------------------------------>
  90. <div class="uploaderBox">
  91. <div class="avatar-upload-container" @mouseenter="hoveringTwo = true" @mouseleave="hoveringTwo = false">
  92. <!-- 上传组件 -->
  93. <el-upload class="avatar-uploader" action="#" :show-file-list="false" :before-upload="beforeAvatarUploadTwo">
  94. <!-- 预览图片 -->
  95. <img v-if="iconSrcSelect" :src="iconSrcSelect" class="avatar">
  96. <div v-else class="chooseImgDiv">
  97. <div>
  98. <img src="@/assets/public/upload/noImage.png">
  99. <div>选择图片</div>
  100. </div>
  101. </div>
  102. </el-upload>
  103. <!-- 删除按钮,当鼠标悬浮时显示 -->
  104. <div v-if="hoveringTwo && iconSrcSelect" class="delete-button" @click="handleDeleteTwo">
  105. <i class="el-icon-delete"></i>
  106. </div>
  107. </div>
  108. </div>
  109. <!--图片上传组件 end ------------------------------------------------------------>
  110. </el-form-item>
  111. <el-form-item label="权重" :label-width="formLabelWidth" prop="sort" class="custom-align-right">
  112. <el-input v-model="form.sort" autocomplete="off" placeholder="请输入权重"></el-input>
  113. </el-form-item>
  114. </div>
  115. </el-form>
  116. <div slot="footer" class="dialog-footer">
  117. <div>
  118. <el-button @click="closeWindow">取 消</el-button>
  119. <el-button type="primary" @click="editToServe" v-if="editBtn==true">编辑</el-button>
  120. <el-button type="primary" @click="addToServe" v-else>提交</el-button>
  121. </div>
  122. </div>
  123. </el-dialog>
  124. <!--弹出框 end------------------------------------------------------------>
  125. </div>
  126. </template>
  127. <script>
  128. //表格标题
  129. import openWindow from '@/utils/open-window';
  130. import tableTitle from './components/tableTitle';
  131. //引入公用样式
  132. import '@/styles/global.less';
  133. export default {
  134. components: {
  135. tableTitle,//表格标题
  136. },
  137. data() {
  138. //0.全局操作 start ------------------------------------------------------------>
  139. const validateEmpty = (rule,value,callback) => {
  140. if (value.length == 0) {
  141. callback(new Error('该项不能为空!'))
  142. } else {
  143. callback()
  144. }
  145. }
  146. const validateArray = (rule,value,callback) => {
  147. if (value.length == 0) {
  148. callback(new Error('该项不能为空!'))
  149. } else {
  150. callback()
  151. }
  152. }
  153. //0.全局操作 end ------------------------------------------------------------>
  154. return {
  155. //1.列表和分页相关 start ------------------------------------------------------------>
  156. tableDivTitle:"菜单列表",
  157. tableData: [],//内容列表
  158. editId:"",//编辑id
  159. radio: '2',//是否为顶级菜单 1=是 2=否
  160. //分页相关 end ------------------------------------------------------------>
  161. //2.弹出框设置 start ------------------------------------------------------------>
  162. //是否显示弹出窗口
  163. windowStatus:false,
  164. formLabelWidth: '120px',
  165. editBtn:false,//当显示编辑按钮的时候,就不显示提交
  166. //弹出框设置 end ------------------------------------------------------------>
  167. //3.弹出框中的表单设置 start ------------------------------------------------------------>
  168. form: {
  169. label:"",//菜单名称
  170. url:"",//菜单路由地址
  171. icon:"",//默认图标
  172. selected_icon:"",//选中时图标
  173. sort:"",//权重
  174. pid_arr:[]//父级id
  175. },
  176. //通过api获的的菜单列表
  177. parentKey:0,
  178. pidArrData:[],
  179. //3.1 上传默认图标
  180. iconSrc:'',//默认图标缩略图
  181. hovering: false,//显示删除图标
  182. iconSrcSelect:'',//选中时图标缩略图
  183. hoveringTwo: false,//显示删除图标
  184. //3.2 表单验证规则
  185. formRules: {
  186. label:[{required:true,trigger:'blur',validator:validateEmpty}],
  187. url:[{required:true,trigger:'blur',validator:validateEmpty}],
  188. icon:[{required:true,trigger:'blur',validator:validateEmpty}],
  189. selected_icon:[{required:true,trigger:'blur',validator:validateEmpty}],
  190. sort:[{required:true,trigger:'blur',validator:validateEmpty}],
  191. pid_arr:[{required:true,trigger:'blur',validator:validateArray}],
  192. },
  193. //弹出框中的表单设置 end ------------------------------------------------------------>
  194. }
  195. },
  196. methods: {
  197. //1.列表和分页相关 start ------------------------------------------------------------>
  198. //1.1 开始请求列表信息方法
  199. getData(){
  200. this.$store.dispatch('menu/getMenuList').then(res=> {
  201. this.tableData = res.data
  202. console.log(this.tableData)
  203. //格式化以后放入级联选择器
  204. let arrData = this.transformData(res.data)
  205. this.pidArrData = arrData;
  206. }).catch(() => {
  207. this.$message({
  208. type: 'warning',
  209. message: '网络错误,请重试!'
  210. });
  211. })
  212. },
  213. //1.2格式化菜单列表
  214. transformData(arrData) {
  215. let that = this;
  216. return arrData.map(item => {
  217. // 创建一个新的对象,替换键名
  218. let newItem = {
  219. label: item.label,
  220. value: item.id,
  221. };
  222. // 如果有 children,则递归处理 children 数组
  223. if (item.children && item.children.length > 0) {
  224. newItem.children = that.transformData(item.children);
  225. }
  226. return newItem;
  227. });
  228. },
  229. //1.2 删除内容
  230. deleteRow(id){
  231. this.$confirm('此操作将永久删除该条数据, 是否继续?', '提示', {
  232. confirmButtonText: '确定',
  233. cancelButtonText: '取消',
  234. type: 'warning'
  235. }).then(() => {
  236. console.log("当前删除:" + id)
  237. this.$store.dispatch('menu/delMenu',{id:id}).then(res=> {
  238. this.getData();
  239. if(res.code==200){
  240. this.$message({
  241. type: 'success',
  242. message: '删除成功!'
  243. });
  244. this.getData();
  245. }else{
  246. this.$message({
  247. type: 'warning',
  248. message: '请先移除子菜单!'
  249. });
  250. }
  251. }).catch(() => {
  252. this.$message({
  253. type: 'warning',
  254. message: '网络错误,请重试!'
  255. });
  256. })
  257. }).catch(() => {
  258. this.$message({
  259. type: 'warning',
  260. message: '已取消删除'
  261. });
  262. });
  263. },
  264. //1.3 回显数据
  265. editRow(data){
  266. this.editId = data.id;
  267. console.log(data)
  268. this.openWindow();
  269. this.form.label = data.label;
  270. this.form.url = data.url;
  271. this.form.icon = data.icon;
  272. this.form.selected_icon = data.selected_icon;
  273. this.form.sort = data.sort;
  274. //回显父级
  275. if(data.pid==0){
  276. //如果是顶级菜单
  277. this.radio = "1";//是顶级菜单
  278. }else{
  279. this.radio = "2";//不是顶级菜单
  280. //如果不是顶级菜单 显示到选择器中
  281. this.parentKey += 1;
  282. console.log(this.pidArrData)
  283. console.log(data.pid)
  284. this.form.pid_arr = [data.pid];
  285. }
  286. //回显示icon
  287. this.iconSrc = data.icon;
  288. this.iconSrcSelect = data.selected_icon;
  289. //显示编辑按钮
  290. this.editBtn = true;
  291. },
  292. //提交
  293. addToServe(){
  294. //先判断是不是顶级菜单
  295. if(this.radio=="1"){
  296. //如果是顶级菜单
  297. this.form.pid_arr = [0];
  298. }
  299. //执行验证
  300. this.$refs.form.validate(valid => {
  301. if (valid) {
  302. this.$store.dispatch('menu/addMenu',this.form).then(res=> {
  303. if(res.code==200){
  304. this.$message({
  305. type: 'success',
  306. message: '成功添加路由!'
  307. });
  308. this.getData();
  309. this.closeWindow();
  310. }
  311. }).catch(() => {
  312. this.$message({
  313. type: 'warning',
  314. message: '网络错误!'
  315. });
  316. });
  317. }
  318. })
  319. },
  320. //修改
  321. editToServe(){
  322. this.$refs.form.validate(valid => {
  323. if (valid) {
  324. let data = this.form;
  325. data.id = this.editId;
  326. this.$store.dispatch('menu/updateMenu',data).then(res=> {
  327. if(res.code==200){
  328. this.$message({
  329. type: 'success',
  330. message: '路由状态已修改!'
  331. });
  332. this.getData();
  333. this.closeWindow();
  334. }
  335. }).catch(() => {
  336. this.$message({
  337. type: 'warning',
  338. message: '操作已取消!'
  339. });
  340. });
  341. }
  342. })
  343. },
  344. //列表和分页相关 end ------------------------------------------------------------>
  345. //2.弹出框设置 start ------------------------------------------------------------>
  346. //2.1 打开弹出框
  347. openWindow() {
  348. this.windowStatus = true;
  349. this.clearToServe();
  350. //显示添加按钮
  351. this.editBtn = false;
  352. },
  353. //2.2 关闭弹出框
  354. closeWindow(){
  355. this.windowStatus = false;
  356. this.clearToServe();
  357. },
  358. //2.3 清理弹出框
  359. clearToServe(){
  360. this.form.label = "";
  361. this.form.url = "";
  362. this.form.icon = "";
  363. this.form.selected_icon = "";
  364. this.form.sort = "";
  365. this.form.pid_arr = [];
  366. //this.pidArrData=[];
  367. this.iconSrc = "";
  368. this.hovering = false;
  369. this.iconSrcSelect = "";
  370. this.hoveringTwo = false;
  371. this.editBtn = false;
  372. },
  373. //弹出框设置 end ------------------------------------------------------------>
  374. //3.添加菜单 start ------------------------------------------------------------>
  375. //3.1 上传默认图标
  376. beforeAvatarUpload(file) {
  377. const isJPG = file.type === 'image/jpeg';
  378. const isPNG = file.type === 'image/png';
  379. const isLt2M = file.size / 1024 / 1024 < 2;
  380. if (!isJPG && !isPNG) {
  381. this.$message.error('上传图片只能是 JPG 或 PNG 格式!');
  382. return false;
  383. }
  384. if (!isLt2M) {
  385. this.$message.error('上传图片大小不能超过 2MB!');
  386. return false;
  387. }
  388. const formData = new FormData();
  389. formData.append('file', file);
  390. this.$store.dispatch('pool/uploadFile',formData).then(res=> {
  391. this.iconSrc = res.data.imgUrl;//显示缩略图
  392. this.form.icon = res.data.imgUrl;//提供表单地址
  393. console.log(res.data.imgUrl)
  394. }).catch(() => {
  395. this.$message({
  396. type: 'info',
  397. message: '网络错误,请重试!'
  398. });
  399. })
  400. // 阻止默认的上传行为
  401. return false;
  402. },
  403. handleDelete() {
  404. // 删除图片
  405. this.form.icon = ''; //清空选中的图片
  406. this.iconSrc = ''; // 清空图片 URL
  407. },
  408. //3.2 上传选中时图标
  409. beforeAvatarUploadTwo(file) {
  410. const isJPG = file.type === 'image/jpeg';
  411. const isPNG = file.type === 'image/png';
  412. const isLt2M = file.size / 1024 / 1024 < 2;
  413. if (!isJPG && !isPNG) {
  414. this.$message.error('上传图片只能是 JPG 或 PNG 格式!');
  415. return false;
  416. }
  417. if (!isLt2M) {
  418. this.$message.error('上传图片大小不能超过 2MB!');
  419. return false;
  420. }
  421. const formData = new FormData();
  422. formData.append('file', file);
  423. this.$store.dispatch('pool/uploadFile',formData).then(res=> {
  424. this.iconSrcSelect = res.data.imgUrl;//显示缩略图
  425. this.form.selected_icon = res.data.imgUrl;//提供表单地址
  426. // 确保 Vue 能够检测到变化
  427. this.$forceUpdate();
  428. }).catch(() => {
  429. this.$message({
  430. type: 'info',
  431. message: '网络错误,请重试!'
  432. });
  433. })
  434. // 阻止默认的上传行为
  435. return false;
  436. },
  437. handleDeleteTwo() {
  438. // 删除图片
  439. this.form.selected_icon = ''; //清空选中的图片
  440. this.iconSrcSelect = ''; // 清空图片 URL
  441. },
  442. //添加菜单 end ------------------------------------------------------------>
  443. },
  444. mounted(){
  445. //1.获得初始数据
  446. this.getData();
  447. }
  448. }
  449. </script>
  450. <style scoped lang="less">
  451. .layerBoxNoBg {
  452. padding:30px 0 0 0;
  453. }
  454. //执行v-deep穿透scope选择器 start------------------------------------------------------------>*/
  455. ::v-deep .custom-form-item > .el-form-item__label {
  456. line-height: 140px !important;
  457. }
  458. ::v-deep .custom-textarea .el-textarea__inner {
  459. resize: none; /* 禁止用户拖拽调整大小 */
  460. }
  461. ::v-deep .custom-align-right .el-form-item__label {
  462. text-align: right; /* 设置标签文字右对齐 */
  463. }
  464. //执行v-deep穿透scope选择器 end------------------------------------------------------------>*/
  465. </style>