refactor: keepalive无效问题修复和代码vue社区代码规范调整

Former-commit-id: f661982d54f1738ff9739f1afc993181a466f052
This commit is contained in:
郝先瑞
2022-04-24 00:08:25 +08:00
parent c0b96bfab8
commit 47ed525fcd
71 changed files with 2814 additions and 1912 deletions

View File

@@ -1,14 +1,13 @@
<template>
<el-config-provider :locale="locale">
<router-view />
<router-view/>
</el-config-provider>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import { ElConfigProvider } from "element-plus";
import { localStorage } from "@/utils/storage";
import useStore from "@/store";
// 导入 Element Plus 语言包
@@ -24,21 +23,11 @@ const locale = ref();
watch(
language,
(value) => {
if (value == "en") {
locale.value = en;
} else {
locale.value = zhCn;
}
},
{
// 初始化立即执行
immediate: true
}
);
onMounted(() => {
const style = localStorage.get("style");
document.documentElement.style.cssText = style as string;
locale.value = value == "en" ? en : zhCn;
}, {
// 初始化立即执行
immediate: true
});
</script>

36
src/api/lab/seata.ts Normal file
View File

@@ -0,0 +1,36 @@
import { SeataFormData } from '@/types'
import request from '@/utils/request'
/**
* 订单支付
* @returns
*/
export function payOrder(data: SeataFormData) {
return request({
url: '/youlai-lab/api/v1/seata/order/_pay',
method: 'post',
data: data
})
}
/**
* 获取Seata模拟数据(包括订单信息、商品信息、会员余额信息)
* @returns
*/
export function getSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data',
method: 'get'
})
}
/**
* 重置Seata模拟数据
* @returns
*/
export function resetSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data/_reset',
method: 'put'
})
}

View File

@@ -7,7 +7,7 @@ import { AxiosPromise } from 'axios'
*/
export function listRoutes() {
return request({
url: '/youlai-admin/api/v2/menus/route',
url: '/youlai-admin/api/v1/menus/route',
method: 'get'
})
}

View File

@@ -7,7 +7,7 @@ import { AxiosPromise } from 'axios'
*
* @param queryParams
*/
export function listMemeberPages(queryParams: MemberQueryParam): AxiosPromise<MemberPageResult> {
export function listMemebersPage(queryParams: MemberQueryParam): AxiosPromise<MemberPageResult> {
return request({
url: '/mall-ums/api/v1/members',
method: 'get',
@@ -20,7 +20,7 @@ export function listMemeberPages(queryParams: MemberQueryParam): AxiosPromise<Me
*
* @param id
*/
export function getMemberFormDetail(id: number) {
export function getMemberDetail(id: number) {
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'get'

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1 +0,0 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>

Before

Width:  |  Height:  |  Size: 418 B

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036347051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M832 128H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h640a64.19 64.19 0 0 0 64-64V192a64.19 64.19 0 0 0-64-64z m0 703.89l-0.11 0.11H192.11l-0.11-0.11V768h640zM832 544H720L605.6 696.54 442.18 435.07 333.25 544H192v-64h114.75l147.07-147.07L610.4 583.46 688 480h144z m0-288H192v-63.89l0.11-0.11h639.78l0.11 0.11z" p-id="5854"></path></svg>

Before

Width:  |  Height:  |  Size: 724 B

View File

@@ -0,0 +1 @@
<svg t="1650624175386" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12087" width="200" height="200"><path d="M879.2064 234.7008l-89.8048 0L789.4016 197.632c0-6.2464-0.6144-12.3904-1.6384-18.3296-2.1504-11.776-6.3488-22.8352-12.1856-32.5632-11.6736-19.3536-29.696-33.4848-50.7904-38.4-5.2224-1.2288-10.6496-1.8432-16.2816-1.8432L144.7936 106.496c-5.5296 0-10.9568 0.6144-16.2816 1.8432-20.992 4.9152-39.1168 18.944-50.7904 38.4-5.8368 9.728-10.0352 20.6848-12.1856 32.5632-1.1264 5.9392-1.6384 11.9808-1.6384 18.3296l0 457.9328c0 6.2464 0.6144 12.3904 1.6384 18.3296 2.1504 11.776 6.3488 22.8352 12.1856 32.4608 11.6736 19.3536 29.696 33.4848 50.7904 38.4 5.2224 1.2288 10.6496 1.8432 16.2816 1.8432l47.2064 0L192 826.368c0 50.0736 36.352 91.0336 80.7936 91.0336l606.3104 0c44.4416 0 80.7936-40.96 80.7936-91.0336L959.8976 426.7008l0 0c0 0 0 0 0 0l0-100.9664C960 275.6608 923.648 234.7008 879.2064 234.7008zM192 325.7344l0 378.2656-47.2064 0c-20.6848 0-38.1952-22.1184-38.1952-48.3328L106.5984 197.632c0-26.2144 17.5104-48.3328 38.1952-48.3328l563.6096 0c20.6848 0 38.1952 22.1184 38.1952 48.3328l0 36.9664-85.2992 0L490.7008 234.5984 272.896 234.5984C228.352 234.7008 192 275.6608 192 325.7344zM661.2992 277.2992l0 378.9824-66.2528-33.0752-19.0464-9.5232-19.0464 9.5232-66.2528 33.0752L490.7008 426.7008l0 0c0 0 0 0 0 0L490.7008 277.2992 661.2992 277.2992zM448 277.2992l0 128L234.7008 405.2992l0-79.6672c0-26.2144 17.5104-48.3328 38.1952-48.3328L448 277.2992zM917.2992 826.368c0 26.2144-17.5104 48.3328-38.1952 48.3328L272.896 874.7008c-20.6848 0-38.1952-22.1184-38.1952-48.3328L234.7008 448l213.2992 0 0 213.2992c0 23.4496 19.2512 42.7008 42.7008 42.7008l85.2992-42.7008 85.2992 42.7008c23.4496 0 42.7008-19.1488 42.7008-42.7008L704 448l213.2992 0L917.2992 826.368zM917.2992 405.2992 704 405.2992l0-128 175.2064 0c20.6848 0 38.1952 22.1184 38.1952 48.3328L917.4016 405.2992z" p-id="12088"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612240746070" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2134" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css"></style></defs><path d="M344.832 474.112H98.048a61.632 61.632 0 0 1-61.44-61.44V165.888c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296a61.632 61.632 0 0 1-61.952 60.928zM98.048 165.888v247.296h247.296V165.888H98.048z m609.792 359.424a60.8 60.8 0 0 1-43.52-17.92L489.728 332.8a60.8 60.8 0 0 1-17.92-43.52 60.8 60.8 0 0 1 17.92-43.52L664.32 71.168a60.8 60.8 0 0 1 43.52-17.92 60.8 60.8 0 0 1 43.52 17.92l174.592 174.592a60.8 60.8 0 0 1 17.92 43.52 60.8 60.8 0 0 1-17.92 43.52L751.36 507.392a60.8 60.8 0 0 1-43.52 17.92z m0-411.136L533.248 288.768 707.84 463.36l174.592-174.592-174.592-174.592zM344.832 960.512H98.048a61.632 61.632 0 0 1-61.44-61.44v-247.296c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296a61.76 61.76 0 0 1-61.952 61.44z m-246.784-308.224v247.296h247.296v-247.296H98.048z m733.184 308.224H583.936a61.632 61.632 0 0 1-61.44-61.44v-247.296c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c0 34.304-27.136 61.44-61.44 61.44z m-246.784-308.224v247.296h247.296v-247.296H584.448z" fill="#666666" p-id="2135"></path></svg>
<svg t="1650624092030" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11056" width="200" height="200"><path d="M374.272 440.832H127.488c-33.792 0-61.44-27.648-61.44-61.44V132.608c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c-0.512 33.792-27.648 60.928-61.952 60.928zM127.488 132.608v247.296h247.296V132.608H127.488zM762.88 492.032c-16.384 0-31.744-6.144-43.52-17.92l-174.592-174.592c-11.776-11.776-17.92-27.136-17.92-43.52s6.144-31.744 17.92-43.52l174.592-174.592c11.776-11.776 27.136-17.92 43.52-17.92s31.744 6.144 43.52 17.92l174.592 174.592c11.776 11.776 17.92 27.136 17.92 43.52s-6.144 31.744-17.92 43.52l-174.592 174.592c-11.776 11.776-27.136 17.92-43.52 17.92z m0-410.624L588.288 256 762.88 430.592 937.472 256 762.88 81.408zM374.272 952.832H127.488c-33.792 0-61.44-27.648-61.44-61.44v-247.296c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c-0.512 34.304-27.648 61.44-61.952 61.44z m-246.784-308.224v247.296h247.296v-247.296H127.488zM886.272 952.832h-247.296c-33.792 0-61.44-27.648-61.44-61.44v-247.296c0-33.792 27.648-61.44 61.44-61.44h247.296c33.792 0 61.44 27.648 61.44 61.44v247.296c0 34.304-27.136 61.44-61.44 61.44z m-246.784-308.224v247.296h247.296v-247.296h-247.296z" p-id="11057"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +1 @@
<svg t="1615363824084" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20947" width="256" height="256"><path d="M662.3 596.1c-42.7 35.9-96.1 55.5-152.5 55.5-56.4 0-109.8-19.6-152.4-55.5-123 53.6-208.5 169.6-208.5 304.1 0 15.4 12.4 27.8 27.8 27.8 1 0 2-0.1 3-0.2H840c1 0.1 2 0.2 3 0.2s2-0.1 3-0.2h3.3v-0.6a27.79 27.79 0 0 0 21.4-27.1c0.1-134.4-85.4-250.4-208.4-304zM277.3 365.3c9.3-45.1 31.4-86.5 64.7-119.8 24.9-24.9 54.3-43.6 86.4-55.3-17.6-54.7-68.7-94.2-129-94.2-74.7 0-135.3 60.7-135.3 135.5 0 67.4 49 123.2 113.2 133.8zM272.4 413.7c0-9.6 0.6-19.2 1.7-28.6h-0.1c-27.5-4.5-53.2-16.5-74.4-34.4C119.6 385.8 64 461.4 64 548.9c0 10 8.1 18.1 18.1 18.1 0.7 0 1.3 0 2-0.1h244.1c-36.1-42.8-55.8-96.5-55.8-153.2zM960 548.9c0-87.6-55.6-163.2-135.6-198.2-22.3 18.8-49.6 31-78.7 35 1.1 9.2 1.6 18.5 1.6 28 0 56.7-19.7 110.4-55.8 153.3h248.4c0.6 0.1 1.3 0.1 2 0.1s1.3 0 2-0.1h2.2v-0.4c7.9-1.9 13.9-9.1 13.9-17.7zM742.5 365.9c66.2-8.8 117.3-65.6 117.3-134.3 0-74.9-60.6-135.5-135.3-135.5-60.8 0-112.3 40.2-129.3 95.5 30.6 11.8 58.7 30 82.6 53.9 33.3 33.4 55.6 75.1 64.7 120.4z" p-id="20948"></path><path d="M302.4 413.7a207.4 207.8 0 1 0 414.8 0 207.4 207.8 0 1 0-414.8 0Z" p-id="20949"></path></svg>
<svg t="1650623852152" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7510" width="200" height="200"><path d="M520.23466666 504.57706667c51.3056-33.51253333 85.2544-91.47733333 85.2544-157.30133334 0-103.4848-84.27306667-187.7568-187.75786666-187.7568S229.9744 243.79093333 229.9744 347.27466667c0 65.7152 33.9488 123.68 85.2544 157.30133333-47.26613333 15.71946667-90.60266667 42.24533333-126.8448 78.4864-61.23946667 61.23946667-94.97066667 142.67413333-94.97066667 229.2384 0 13.3184 10.69866667 24.016 24.016 24.016s24.01493333-10.69866667 24.01493333-24.016c0-152.27946667 123.89866667-276.17706667 276.17813334-276.17706667S693.80053333 660.02133333 693.80053333 812.30186667c0 13.3184 10.6976 24.016 24.01493333 24.016s24.016-10.69866667 24.016-24.016c0-86.56426667-33.7312-167.99893333-94.97066666-229.23733334-36.02346667-36.13333333-79.36-62.768-126.6272-78.48746666zM278.00533333 347.38453333c0-77.06666667 62.65813333-139.72586667 139.72586667-139.72586666s139.72693333 62.65813333 139.72693333 139.72586666-62.6592 139.72693333-139.72693333 139.72693334c-76.95786667 0-139.72586667-62.6592-139.72586667-139.72693334z" p-id="7511"></path><path d="M871.40586666 599.0016a323.7312 323.7312 0 0 0-150.6432-119.8592c27.072-40.28053333 41.4816-87.76533333 41.04533334-136.77866667-0.43733333-59.60213333-22.70613333-116.69333333-62.54933334-160.79466666-8.95146667-9.824-24.1248-10.5888-33.94986666-1.63733334-9.824 8.95146667-10.58773333 24.1248-1.63733334 33.9488 66.69866667 73.57546667 67.13493333 185.13813333 0.9824 259.47733334-1.52746667 1.74613333-2.83733333 3.71093333-3.712 5.67466666-1.2 1.856-2.0736 3.93066667-2.72853333 6.22293334-3.49226667 12.77226667 4.14933333 25.9808 16.92053333 29.47306666 131.97546667 35.36853333 217.23093333 159.59466667 202.93013334 295.17226667-1.41866667 13.20853333 8.18773333 24.9984 21.39626666 26.41706667 0.87253333 0.1088 1.74613333 0.1088 2.50986667 0.1088 12.11733333 0 22.59733333-9.16906667 23.9072-21.504 7.968-75.86773333-11.3536-152.608-54.47146667-215.92106667z" p-id="7512"></path></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg t="1650624655184" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22328" width="200" height="200"><path d="M924.145781 233.089709l0-3.215228-0.642636-0.642636 0-2.571568-0.642636-0.642636 0-0.642636-0.642636-0.642636-0.642636 0-0.642636-0.642636 0-0.642636-1.928932-1.928932 0-0.642636-0.642636-0.642636 0-1.286296-1.928932 0-0.642636-0.642636-1.286296 0-0.642636-0.642636 0-0.642636-0.642636 0 0-0.642636-0.642636 0-0.642636-0.642636-0.642636 0 0-0.642636-2.571568 0 0-0.642636-5.14416 0-0.642636 0.642636-0.642636 0 0 0.642636-1.286296 0-0.642636 0.642636-1.286296 0L112.707968 515.999081c-10.287297 3.857864-15.431457 14.788821-11.573593 25.718755 2.571568 5.786797 7.073092 9.644661 12.216229 11.573593l235.972363 94.517677 24.433482 135.667889c1.928932 10.930957 12.216229 17.36039 22.50455 16.074094 4.500501-0.642636 8.358365-3.215228 10.930957-6.429433l87.444585-87.444585 178.104397 71.370491c10.287297 3.857864 21.218254-0.642636 25.718755-10.287297l223.756133-523.383258 0.642636-0.642636 0-0.642636 0-1.286296 0-5.14416L924.145781 233.089709 924.145781 233.089709zM364.112812 610.516758 364.112812 610.516758l-190.32165-75.870991 604.39841-230.829226L364.112812 610.516758 364.112812 610.516758zM405.263024 738.468918 405.263024 738.468918l-12.859889-74.585719 62.368466 25.076118L405.263024 738.468918 405.263024 738.468918zM670.169369 733.325781 670.169369 733.325781 406.54932 627.877147l452.012767-334.347904L670.169369 733.325781 670.169369 733.325781z" p-id="22329"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg t="1650625601015" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38305" width="200" height="200"><path d="M192 128h-128v768h896V384h-384V128h-128v256h-256V128zM384 320v-256h256v256h384v640H0V64h256v256h128z" p-id="38306"></path><path d="M640 576v128h128v-128H640zM576 512h256v256h-256V512z" p-id="38307"></path></svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -0,0 +1 @@
<svg t="1650625555592" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36820" width="200" height="200"><path d="M586.666667 494.933333l-8.533334 40.533334-44.8-119.466667-19.2 2.133333-29.866666 57.6H448l-8.533333 19.2-25.6 51.2-29.866667-72.533333-17.066667 6.4 38.4 93.866667h17.066667l38.4-78.933334H497.066667l27.733333-51.2 46.933333 125.866667h19.2l14.933334-74.666667h51.2v-19.2h-66.133334l-4.266666 19.2zM512 283.733333c-117.333333 0-213.333333 96-213.333333 213.333334s96 213.333333 213.333333 213.333333 213.333333-96 213.333333-213.333333c0-119.466667-96-213.333333-213.333333-213.333334z m0 401.066667c-104.533333 0-189.866667-85.333333-189.866667-189.866667s85.333333-189.866667 189.866667-189.866666 189.866667 85.333333 189.866667 189.866666-85.333333 189.866667-189.866667 189.866667z m381.866667-460.8c-44.8-14.933333-134.4-44.8-179.2-66.133333-89.6-44.8-123.733333-74.666667-157.866667-100.266667-12.8-10.666667-27.733333-14.933333-44.8-14.933333s-32 4.266667-44.8 12.8c-34.133333 25.6-68.266667 55.466667-157.866667 100.266666-44.8 21.333333-134.4 51.2-179.2 66.133334-29.866667 10.666667-46.933333 36.266667-44.8 66.133333 8.533333 98.133333 32 288 89.6 405.333333 68.266667 134.4 236.8 238.933333 302.933334 279.466667 10.666667 6.4 21.333333 8.533333 34.133333 8.533333s23.466667-2.133333 34.133333-8.533333c66.133333-40.533333 234.666667-145.066667 302.933334-279.466667 57.6-117.333333 81.066667-307.2 89.6-401.066666 2.133333-32-17.066667-59.733333-44.8-68.266667zM810.666667 674.133333c-42.666667 85.333333-138.666667 172.8-285.866667 262.4-4.266667 0-8.533333 2.133333-12.8 2.133334s-8.533333-2.133333-10.666667-4.266667c-149.333333-87.466667-245.333333-177.066667-288-260.266667C153.6 554.666667 132.266667 347.733333 128 288c-2.133333-14.933333 12.8-21.333333 17.066667-23.466667l17.066666-6.4c49.066667-17.066667 125.866667-42.666667 168.533334-64 81.066667-40.533333 119.466667-70.4 151.466666-93.866666 4.266667-4.266667 8.533333-6.4 12.8-10.666667 0-2.133333 6.4-4.266667 14.933334-4.266667h4.266666c8.533333 0 14.933333 2.133333 17.066667 4.266667 4.266667 4.266667 8.533333 6.4 12.8 10.666667 29.866667 23.466667 68.266667 53.333333 151.466667 93.866666 42.666667 21.333333 117.333333 46.933333 168.533333 64l17.066667 6.4c4.266667 2.133333 17.066667 6.4 17.066666 23.466667-6.4 59.733333-27.733333 266.666667-87.466666 386.133333z" p-id="36821"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547360688278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" fill="#bfbfbf" p-id="6718"></path></svg>

Before

Width:  |  Height:  |  Size: 757 B

View File

@@ -1 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
<svg t="1650625155645" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="30436" width="200" height="200"><path d="M848 712H318.4c-25.6 0-46.4-19.2-48-41.6L240 201.6c-1.6-25.6-16-46.4-38.4-57.6l-48-20.8c-12.8-4.8-25.6 0-32 12.8s0 25.6 12.8 32l46.4 20.8c6.4 3.2 11.2 9.6 11.2 17.6l28.8 468.8c3.2 48 46.4 86.4 96 86.4H848c12.8 0 24-11.2 24-24s-11.2-25.6-24-25.6z" p-id="30437"></path><path d="M884.8 265.6c-14.4-16-35.2-25.6-57.6-25.6H337.6c-12.8 0-24 11.2-24 24s11.2 24 24 24h489.6c8 0 16 3.2 20.8 9.6 4.8 6.4 8 14.4 8 22.4l-38.4 211.2v1.6c-1.6 14.4-12.8 24-27.2 25.6l-420.8 32c-12.8 1.6-22.4 12.8-22.4 25.6 1.6 12.8 11.2 22.4 24 22.4h1.6l419.2-32c36.8-3.2 67.2-30.4 70.4-67.2l38.4-212.8v-1.6c4.8-20.8-1.6-43.2-16-59.2z" p-id="30438"></path><path d="M305.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" p-id="30439"></path><path d="M753.6 856m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z" p-id="30440"></path></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 955 B

View File

@@ -1 +0,0 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>

Before

Width:  |  Height:  |  Size: 906 B

View File

@@ -1 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
<svg t="1650623941490" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9166" width="200" height="200"><path d="M977.454545 558.545455h-34.90909v-104.727273c0-44.218182-37.236364-81.454545-81.454546-81.454546H546.909091v-93.090909H744.727273c25.6 0 46.545455-20.945455 46.545454-46.545454V93.090909c0-25.6-20.945455-46.545455-46.545454-46.545454H279.272727c-25.6 0-46.545455 20.945455-46.545454 46.545454v139.636364c0 25.6 20.945455 46.545455 46.545454 46.545454h197.818182v93.090909H162.909091c-44.218182 0-81.454545 37.236364-81.454546 81.454546V558.545455H46.545455c-25.6 0-46.545455 20.945455-46.545455 46.545454v325.818182c0 25.6 20.945455 46.545455 46.545455 46.545454h139.636363c25.6 0 46.545455-20.945455 46.545455-46.545454V605.090909c0-25.6-20.945455-46.545455-46.545455-46.545454H151.272727v-104.727273c0-6.981818 4.654545-11.636364 11.636364-11.636364h314.181818v116.363637H442.181818c-25.6 0-46.545455 20.945455-46.545454 46.545454v325.818182c0 25.6 20.945455 46.545455 46.545454 46.545454h139.636364c25.6 0 46.545455-20.945455 46.545454-46.545454V605.090909c0-25.6-20.945455-46.545455-46.545454-46.545454h-34.909091v-116.363637H861.090909c6.981818 0 11.636364 4.654545 11.636364 11.636364V558.545455H837.818182c-25.6 0-46.545455 20.945455-46.545455 46.545454v325.818182c0 25.6 20.945455 46.545455 46.545455 46.545454h139.636363c25.6 0 46.545455-20.945455 46.545455-46.545454V605.090909c0-25.6-20.945455-46.545455-46.545455-46.545454zM162.909091 628.363636v279.272728H69.818182V628.363636h93.090909z m395.636364 0v279.272728h-93.09091V628.363636h93.09091zM302.545455 209.454545V116.363636h418.90909v93.090909H302.545455z m651.636363 698.181819h-93.090909V628.363636h93.090909v279.272728z" p-id="9167"></path></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +1 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
<svg t="1650622753800" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4947" width="200" height="200"><path d="M599.378 958.454H424.62c-165.805 0-296.769 0-296.769-86.024v-17.178c0-161.188 133.115-292.258 296.77-292.258h174.756c163.619 0 296.769 131.102 296.769 292.258v17.178c0 86.024-137.557 86.024-296.768 86.024z m-176.39-346.981c-137.625 0-249.608 109.935-249.608 245.098v17.491c0 35.046 144.255 35.046 249.608 35.046h177.985c87.207 0 249.645 0 249.645-35.046v-17.491c0-135.163-112.018-245.098-249.645-245.098H422.988z m80.266-83.526c-129.923 0-235.555-104.14-235.555-232.12 0-128.015 105.632-232.119 235.555-232.119s235.554 104.104 235.554 232.12c0 127.978-105.7 232.12-235.554 232.12zM316.246 295.098c0 101.224 83.91 183.572 187.008 183.572 103.133 0 187.042-82.348 187.042-183.572 0-101.19-83.909-183.502-187.042-183.502-103.134 0-187.008 82.311-187.008 183.502z m0 17.767" fill="" p-id="4948"></path></svg>

Before

Width:  |  Height:  |  Size: 440 B

After

Width:  |  Height:  |  Size: 959 B

View File

@@ -1 +0,0 @@
<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +0,0 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,55 @@
<template>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="router-fade" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
</section>
</template>
<script setup lang="ts">
import { computed } from "vue";
import useStore from "@/store";
const { tagsView } = useStore();
const cachedViews = computed(() => tagsView.cachedViews);
</script>
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header+.app-main {
padding-top: 50px;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
}
.fixed-header+.app-main {
padding-top: 84px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<div class="navbar">
<hamburger
id="hamburger-container"
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device !== 'mobile'">
<!-- <search id="header-search" class="right-menu-item" />
<error-log class="errLog-container right-menu-item hover-effect" />-->
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<el-dropdown
class="avatar-container right-menu-item hover-effect"
trigger="click"
>
<div class="avatar-wrapper">
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/">
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/hxrui">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://gitee.com/haoxr">
<el-dropdown-item>{{ $t("navbar.gitee") }}</el-dropdown-item>
</a>
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/">
<el-dropdown-item>{{ $t("navbar.document") }}</el-dropdown-item>
</a>
<el-dropdown-item divided @click="logout">
{{ $t("navbar.logout") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessageBox } from "element-plus";
import useStore from "@/store";
// 组件依赖
import Breadcrumb from "@/components/Breadcrumb/index.vue";
import Hamburger from "@/components/Hamburger/index.vue";
import Screenfull from "@/components/Screenfull/index.vue";
import SizeSelect from "@/components/SizeSelect/index.vue";
import LangSelect from "@/components/LangSelect/index.vue";
// 图标依赖
import { CaretBottom } from "@element-plus/icons-vue";
const { app, user } = useStore();
const route = useRoute();
const router = useRouter();
const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);
const avatar = computed(() => user.avatar);
function toggleSideBar() {
app.toggleSidebar();
}
function logout() {
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
user.logout().then(() => {
router.push(`/login?redirect=${route.fullPath}`);
});
});
}
</script>
<style lang="scss" scoped>
ul {
list-style: none;
margin: 0;
padding: 0;
}
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<div class="drawer-container">
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>主题颜色</span>
<div style="float: right; height: 26px; margin: -3px 8px 0 0">
<theme-picker @change="themeChange" />
</div>
</div>
<div class="drawer-item">
<span>开启 Tags-View</span>
<el-switch v-model="tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>固定 Header</span>
<el-switch v-model="fixedHeader" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>侧边栏 Logo</span>
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, toRefs, watch } from "vue";
import ThemePicker from "@/components/ThemePicker/index.vue";
import useStore from "@/store";
const { setting } = useStore();
const state = reactive({
fixedHeader: setting.fixedHeader,
tagsView: setting.tagsView,
sidebarLogo: setting.sidebarLogo,
});
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
function themeChange(val: any) {
setting.changeSetting({ key: "theme", value: val });
}
watch(
() => state.fixedHeader,
(value) => {
setting.changeSetting({ key: "fixedHeader", value: value });
}
);
watch(
() => state.tagsView,
(value) => {
setting.changeSetting({ key: "tagsView", value: value });
}
);
watch(
() => state.sidebarLogo,
(value) => {
setting.changeSetting({ key: "sidebarLogo", value: value });
}
);
</script>
<style lang="scss" scoped>
.drawer-container {
padding: 24px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
line-height: 22px;
}
.drawer-item {
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
padding: 12px 0;
}
.drawer-switch {
float: right;
}
.job-link {
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<a
v-if="isExternal(to)"
:href="to"
target="_blank"
rel="noopener"
>
<slot />
</a>
<div
v-else
@click="push"
>
<slot />
</div>
</template>
<script lang="ts">
import {computed, defineComponent} from 'vue'
import { isExternal } from '@/utils/validate'
import { useRouter } from 'vue-router'
import useStore from "@/store";
const {app}=useStore()
const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);
export default defineComponent({
props: {
to: {
type: String,
required: true
}
},
setup(props) {
const router = useRouter()
const push = () => {
if (device.value === 'mobile' && sidebar.value.opened == true) {
app.closeSideBar(false)
}
router.push(props.to).catch((err) => {
console.log(err)
})
}
return {
push,
isExternal
}
}
})
</script>

View File

@@ -0,0 +1,85 @@
<template>
<div class="sidebar-logo-container" :class="{ 'collapse': isCollapse }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 class="sidebar-title">{{ title }} </h1>
</router-link>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, toRefs } from 'vue';
const props = defineProps({
collapse: {
type: Boolean,
required: true
}
})
const state = reactive({
isCollapse: props.collapse
})
const { isCollapse } = toRefs(state)
const title = ref("vue3-element-admin")
const logo = ref("https://www.youlai.tech/files/blog/logo.png")
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 12px;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div v-if="!item.meta || !item.meta.hidden">
<template
v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&(!item.meta|| !item.meta.alwaysShow)"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<svg-icon v-if="onlyOneChild.meta && onlyOneChild.meta.icon" :icon-class="onlyOneChild.meta.icon"/>
<template #title>
{{ generateTitle(onlyOneChild.meta.title ) }}
</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else :index="resolvePath(item.path)" popper-append-to-body>
<!-- popper-append-to-body -->
<template #title>
<svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
<span v-if="item.meta && item.meta.title">{{generateTitle(item.meta.title) }}</span>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:item="child"
:is-nest="true"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup lang="ts">
import { ref} from "vue";
import path from 'path-browserify'
import {isExternal} from '@/utils/validate'
import AppLink from './Link.vue'
import { generateTitle } from '@/utils/i18n'
import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
required: false
},
basePath: {
type: String,
required: true
}
})
const onlyOneChild = ref();
function hasOneShowingChild(children = [] as any, parent: any) {
if (!children) {
children = [];
}
const showingChildren = children.filter((item: any) => {
if (item.meta && item.meta.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = {...parent, path: '', noShowingChildren: true}
return true
}
return false
}
function resolvePath(routePath: string) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
return path.resolve(props.basePath, routePath)
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse"/>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
:unique-opened="false"
:collapse-transition="false"
mode="vertical">
<sidebar-item
v-for="route in routes"
:item="route"
:key="route.path"
:base-path="route.path"
:is-collapse="isCollapse"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import {computed} from "vue";
import {useRoute} from 'vue-router'
import SidebarItem from './SidebarItem.vue'
import Logo from './Logo.vue'
import variables from '@/styles/variables.module.scss'
import useStore from "@/store";
const {permission,setting,app} =useStore();
const route =useRoute()
const routes =computed(() => permission.routes)
const showLogo = computed(() => setting.sidebarLogo)
const isCollapse = computed(() => !app.sidebar.opened)
const activeMenu = computed(() => {
const {meta, path} = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu as string
}
return path
})
</script>

View File

@@ -0,0 +1,132 @@
<template>
<el-scrollbar
ref="scrollContainerRef"
:vertical="false"
class="scroll-container"
@wheel.prevent="handleScroll"
>
<slot />
</el-scrollbar>
</template>
<script setup lang="ts">
import {
ref,
computed,
onMounted,
onBeforeUnmount,
getCurrentInstance
} from "vue";
import { TagView } from "@/types";
import useStore from "@/store";
const tagAndTagSpacing = ref(4);
const scrollContainerRef = ref(null);
const { tagsView } = useStore();
const visitedViews = computed(() => tagsView.visitedViews);
const { ctx } = getCurrentInstance() as any;
const scrollWrapper = computed(() => {
return (scrollContainerRef.value as any).$refs.wrap as HTMLElement;
});
onMounted(() => {
//scrollWrapper.value.addEventListener('scroll', emitScroll, true);
});
onBeforeUnmount(() => {
// scrollWrapper.value.removeEventListener('scroll', emitScroll);
});
function handleScroll(e: WheelEvent) {
const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
scrollWrapper.value.scrollLeft =
scrollWrapper.value.scrollLeft + eventDelta / 4;
}
function moveToTarget(currentTag: TagView) {
const $container = ctx.$refs.scrollContainer.$el;
const $containerWidth = $container.offsetWidth;
const $scrollWrapper = scrollWrapper.value;
let firstTag = null;
let lastTag = null;
// find first tag and last tag
if (visitedViews.value.length > 0) {
firstTag = visitedViews.value[0];
lastTag = visitedViews.value[visitedViews.value.length - 1];
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0;
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
} else {
const tagListDom = document.getElementsByClassName("tags-view-item");
const currentIndex = visitedViews.value.findIndex(
(item) => item === currentTag
);
let prevTag = null;
let nextTag = null;
for (const k in tagListDom) {
if (k !== "length" && Object.hasOwnProperty.call(tagListDom, k)) {
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex - 1].path
) {
prevTag = tagListDom[k];
}
if (
(tagListDom[k] as any).dataset.path ===
visitedViews.value[currentIndex + 1].path
) {
nextTag = tagListDom[k];
}
}
}
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft =
(nextTag as any).offsetLeft +
(nextTag as any).offsetWidth +
tagAndTagSpacing.value;
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft =
(prevTag as any).offsetLeft - tagAndTagSpacing.value;
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
}
}
}
defineExpose({
moveToTarget,
});
</script>
<style lang="scss" scoped>
.scroll-container {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
}
}
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
}
</style>

View File

@@ -0,0 +1,392 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link v-for="tag in visitedViews" :key="tag.path" :class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" class="tags-view-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)">
{{ generateTitle(tag.meta.title) }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
<Close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
</span>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">
<refresh-right style="width: 1em; height: 1em" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<close style="width: 1em; height: 1em" />
关闭
</li>
<li @click="closeOtherTags">
<circle-close style="width: 1em; height: 1em" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<back style="width: 1em; height: 1em" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<right style="width: 1em; height: 1em" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<circle-close style="width: 1em; height: 1em" />
关闭所有
</li>
</ul>
</div>
</template>
<script setup lang="ts" >
import {
computed,
getCurrentInstance,
nextTick,
ref,
watch,
onMounted,
ComponentInternalInstance,
} from "vue";
import path from "path-browserify";
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
import { TagView } from "@/types";
import ScrollPane from "./ScrollPane.vue";
import {
Close,
RefreshRight,
CircleClose,
Back,
Right,
} from "@element-plus/icons-vue";
import { generateTitle } from "@/utils/i18n";
import useStore from "@/store";
const { tagsView, permission } = useStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance; // 获取当前组件实例
const router = useRouter();
const route = useRoute();
const visitedViews = computed<any>(() => tagsView.visitedViews);
const routes = computed<any>(() => permission.routes);
const affixTags = ref([]);
const visible = ref(false);
const selectedTag = ref({});
const scrollPaneRef = ref();
const left = ref(0);
const top = ref(0);
watch(route, () => {
addTags();
moveToCurrentTag();
});
watch(visible, (value) => {
if (value) {
document.body.addEventListener("click", closeMenu);
} else {
document.body.removeEventListener("click", closeMenu);
}
});
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
let tags: TagView[] = [];
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path);
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
});
}
if (route.children) {
const childTags = filterAffixTags(route.children, route.path);
if (childTags.length >= 1) {
tags = tags.concat(childTags);
}
}
});
return tags;
}
function initTags() {
const res = filterAffixTags(routes.value) as [];
affixTags.value = res;
for (const tag of res) {
// Must have tag name
if ((tag as TagView).name) {
tagsView.addVisitedView(tag);
}
}
}
function addTags() {
if (route.name) {
tagsView.addView(route);
}
}
function moveToCurrentTag() {
const tags = getCurrentInstance()?.refs.tag as any[];
nextTick(() => {
if (tags === null || tags === undefined || !Array.isArray(tags)) {
return;
}
for (const tag of tags) {
if ((tag.to as TagView).path === route.path) {
(scrollPaneRef.value as any).value.moveToTarget(tag);
// when query is different then update
if ((tag.to as TagView).fullPath !== route.fullPath) {
tagsView.updateVisitedView(route);
}
}
}
});
}
function isActive(tag: TagView) {
return tag.path === route.path;
}
function isAffix(tag: TagView) {
return tag.meta && tag.meta.affix;
}
function isFirstView() {
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[1].fullPath ||
(selectedTag.value as TagView).fullPath === "/index"
);
} catch (err) {
return false;
}
}
function isLastView() {
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[visitedViews.value.length - 1].fullPath
);
} catch (err) {
return false;
}
}
function refreshSelectedTag(view: TagView) {
tagsView.delCachedView(view);
const { fullPath } = view;
nextTick(() => {
router.replace({ path: "/redirect" + fullPath }).catch((err) => {
console.warn(err);
});
});
}
function toLastView(visitedViews: TagView[], view?: any) {
const latestView = visitedViews.slice(-1)[0];
if (latestView && latestView.fullPath) {
router.push(latestView.fullPath);
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === "Dashboard") {
// to reload home page
router.replace({ path: "/redirect" + view.fullPath });
} else {
router.push("/");
}
}
}
function closeSelectedTag(view: TagView) {
tagsView.delView(view).then((res: any) => {
if (isActive(view)) {
toLastView(res.visitedViews, view);
}
});
}
function closeLeftTags() {
tagsView.delLeftViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
}
function closeRightTags() {
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (
!res.visitedViews.find((item: any) => item.fullPath === route.fullPath)
) {
toLastView(res.visitedViews);
}
});
}
function closeOtherTags() {
tagsView.delOtherViews(selectedTag.value).then(() => {
moveToCurrentTag();
});
}
function closeAllTags(view: TagView) {
tagsView.delRightViews(selectedTag.value).then((res: any) => {
if (affixTags.value.some((tag: any) => tag.path === route.path)) {
return;
}
toLastView(res.visitedViews, view);
});
}
function openMenu(tag: TagView, e: MouseEvent) {
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
const maxLeft = offsetWidth - menuMinWidth; // left boundary
const l = e.clientX - offsetLeft + 15; // 15: margin right
if (l > maxLeft) {
left.value = maxLeft;
} else {
left.value = l;
}
top.value = e.clientY;
visible.value = true;
selectedTag.value = tag;
}
function closeMenu() {
visible.value = false;
}
function handleScroll() {
closeMenu();
}
onMounted(() => {
initTags();
addTags();
});
</script>
<style lang='scss' scoped>
.tags-view-container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: "";
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
width: 16px;
height: 16px;
vertical-align: 0px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(0.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
width: 12px !important;
height: 12px !important;
}
}
}
}
</style>

View File

@@ -0,0 +1,4 @@
export { default as Navbar } from './Navbar.vue'
export { default as AppMain } from './AppMain.vue'
export { default as Settings } from './Settings/index.vue'
export { default as TagsView } from './TagsView/index.vue'

105
src/layout copy/index.vue Normal file
View File

@@ -0,0 +1,105 @@
<template>
<div :class="classObj" class="app-wrapper">
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<Sidebar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<RightPanel v-if="showSettings">
<settings />
</RightPanel>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watchEffect } from "vue";
import { useWindowSize } from "@vueuse/core";
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
import Sidebar from "./components/Sidebar/index.vue";
import RightPanel from "@/components/RightPanel/index.vue";
import useStore from "@/store";
const { width } = useWindowSize();
const WIDTH = 992;
const { app, setting } = useStore();
const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);
const needTagsView = computed(() => setting.tagsView);
const fixedHeader = computed(() => setting.fixedHeader);
const showSettings = computed(() => setting.showSettings);
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,
openSidebar: sidebar.value.opened,
withoutAnimation: sidebar.value.withoutAnimation,
mobile: device.value === "mobile",
}));
watchEffect(() => {
if (width.value < WIDTH) {
app.toggleDevice("mobile");
app.closeSideBar(true);
} else {
app.toggleDevice("desktop");
}
});
function handleClickOutside() {
app.closeSideBar(false);
}
</script>
<style lang="scss" scoped>
@import "@/styles/mixin.scss";
@import "@/styles/variables.module.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
}
.mobile .fixed-header {
width: 100%;
}
</style>

View File

@@ -3,7 +3,7 @@
<router-view v-slot="{ Component, route }">
<transition name="router-fade" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.path" />
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
@@ -29,7 +29,7 @@ const cachedViews = computed(() => tagsView.cachedViews);
overflow: hidden;
}
.fixed-header + .app-main {
.fixed-header+.app-main {
padding-top: 50px;
}
@@ -39,7 +39,7 @@ const cachedViews = computed(() => tagsView.cachedViews);
min-height: calc(100vh - 84px);
}
.fixed-header + .app-main {
.fixed-header+.app-main {
padding-top: 84px;
}
}

View File

@@ -30,7 +30,7 @@ const state = reactive({
const { isCollapse } = toRefs(state)
const title = ref("vue3-element-admin")
const logo = ref("https://s2.loli.net/2022/04/07/hyquWXELOoYvlP6.png")
const logo = ref("https://www.youlai.tech/files/blog/logo.png")
</script>
@@ -61,7 +61,6 @@ const logo = ref("https://s2.loli.net/2022/04/07/hyquWXELOoYvlP6.png")
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
@@ -73,6 +72,7 @@ const logo = ref("https://s2.loli.net/2022/04/07/hyquWXELOoYvlP6.png")
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
margin-left: 12px;
}
}

View File

@@ -1,37 +1,16 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane
ref="scrollPaneRef"
class="tags-view-wrapper"
@scroll="handleScroll"
>
<router-link
v-for="tag in visitedViews"
:key="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
<router-link v-for="tag in visitedViews" :key="tag.path" :class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" class="tags-view-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)">
{{ generateTitle(tag.meta.title) }}
<span
v-if="!isAffix(tag)"
class="el-icon-close"
@click.prevent.stop="closeSelectedTag(tag)"
>
<Close
class="el-icon-close"
style="width: 1em; height: 1em; vertical-align: middle"
/>
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
<Close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
</span>
</router-link>
</scroll-pane>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">
<refresh-right style="width: 1em; height: 1em" />
刷新
@@ -68,7 +47,7 @@ import {
ref,
watch,
onMounted,
ComponentInternalInstance,
ComponentInternalInstance,
} from "vue";
import path from "path-browserify";
@@ -155,7 +134,6 @@ function addTags() {
if (route.name) {
tagsView.addView(route);
}
return false;
}
function moveToCurrentTag() {
@@ -188,7 +166,7 @@ function isFirstView() {
try {
return (
(selectedTag.value as TagView).fullPath ===
visitedViews.value[1].fullPath ||
visitedViews.value[1].fullPath ||
(selectedTag.value as TagView).fullPath === "/index"
);
} catch (err) {
@@ -303,7 +281,7 @@ function handleScroll() {
onMounted(() => {
initTags();
addTags();
addTags();
});
</script>

View File

@@ -1,11 +0,0 @@
export interface PageQueryParam {
pageNum: number,
pageSize: number
}
export interface PageResult<T> {
list: T,
total: number
}

View File

@@ -1,56 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 订单查询参数类型声明
*/
export interface OrderQueryParam extends PageQueryParam {
orderSn: string | undefined;
status: number | undefined;
}
/**
* 订单分页列表项声明
*/
export interface Order {
id: string;
orderSn: string;
totalAmount: string;
payAmount: string;
payType: number;
status: number;
totalQuantity: number;
gmtCreate: string;
memberId: string;
sourceType: number;
orderItems: OrderItem[];
}
export interface OrderItem {
id: string;
orderId: string;
skuId: string;
skuName: string;
picUrl: string;
price: string;
count: number;
totalAmount: number;
}
/**
* 订单分页项类型声明
*/
export type OrderPageResult = PageResult<Order[]>
/**
* 订单表单类型声明
*/
export interface OrderDetail {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@@ -1,34 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 品牌查询参数类型声明
*/
export interface BrandQueryParam extends PageQueryParam {
name?: string
}
/**
* 品牌分页列表项声明
*/
export interface BrandItem {
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
* 品牌分页项类型声明
*/
export type BrandPageResult = PageResult<BrandItem[]>
/**
* 品牌表单类型声明
*/
export interface BrandFormData {
id: number | undefined,
name: string,
logoUrl: string,
sort: number
}

View File

@@ -1,72 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 商品查询参数类型声明
*/
export interface GoodsQueryParam extends PageQueryParam {
name?: stirng,
categoryId?: number
}
/**
* 商品列表项类型声明
*/
export interface GoodsItem {
id: string;
name: string;
categoryId?: any;
brandId?: any;
originPrice: string;
price: string;
sales: number;
picUrl?: any;
album?: any;
unit?: any;
description: string;
detail: string;
status?: any;
categoryName: string;
brandName: string;
skuList: SkuItem[];
}
/**
* 商品规格项类型声明
*/
export interface SkuItem {
id: string;
skuSn?: any;
name: string;
spuId?: any;
specIds: string;
price: string;
stockNum: number;
lockedStockNum?: any;
picUrl?: any;
}
/**
* 商品分页项类型声明
*/
export type GoodsPageResult = PageResult<GoodsItem[]>
/**
* 商品表单数据类型声明
*/
export interface GoodsDetail {
id: string,
name: string,
categoryId: string,
brandId: string,
originPrice: number,
price: number,
picUrl: string,
album: string[],
description: string,
detail: string,
attrList: any[],
specList: any[],
skuList: any[]
}

View File

@@ -1,38 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 广告查询参数类型声明
*/
export interface AdvertQueryParam extends PageQueryParam {
title?: string
}
/**
* 广告分页列表项声明
*/
export interface AdvertItem {
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
* 广告分页项类型声明
*/
export type AdvertPageResult = PageResult<AdvertItem[]>
/**
* 广告表单类型声明
*/
export interface AdvertFormData {
id?: number;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@@ -1,50 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 客户端查询参数类型声明
*/
export interface ClientQueryParam extends PageQueryParam {
/**
* 客户端名称
*/
clientId: string | undefined
}
/**
* 客户端分页列表项声明
*/
export interface ClientItem {
clientId: string;
clientSecret: string;
resourceIds: string;
scope: string;
authorizedGrantTypes: string;
webServerRedirectUri?: any;
authorities?: any;
accessTokenValidity: number;
refreshTokenValidity: number;
additionalInformation?: any;
autoapprove: string;
}
/**
* 客户端分页项类型声明
*/
export type ClientPageResult = PageResult<ClientItem[]>
/**
* 客户端表单类型声明
*/
export interface ClientFormData {
authorizedGrantTypes: string;
clientId: string;
clientSecret: string;
accessTokenValidity: string;
refreshTokenValidity: string;
webServerRedirectUri: string;
authorities: string;
additionalInformation: string;
autoapprove: string;
scope:string;
}

View File

@@ -1,36 +0,0 @@
/**
* 部门查询参数类型声明
*/
export interface DeptQueryParam {
name: string | undefined,
status: number | undefined
}
/**
* 部门列表项声明
*/
export interface DeptItem {
id: string;
name: string;
parentId: string;
treePath: string;
sort: number;
status: number;
leader?: string;
mobile?: string;
email?: string;
children: DeptItem[];
}
/**
* 部门表单类型声明
*/
export interface DeptFormData {
id?: string,
parentId: string,
name: string,
sort: number,
status: number
}

View File

@@ -1,87 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 字典查询参数类型声明
*/
export interface DictQueryParam extends PageQueryParam {
/**
* 字典名称
*/
name: string | undefined
}
/**
* 字典分页列表项声明
*/
export interface Dict {
id: number;
code: string;
name: string;
status: number;
remark: string;
}
/**
* 字典分页项类型声明
*/
export type DictPageResult = PageResult<Dict[]>
/**
* 字典表单类型声明
*/
export interface DictFormData {
id: number | undefined,
name: string,
code: string,
status: number,
remark: string
}
/**
* 字典项查询参数类型声明
*/
export interface DictItemQueryParam extends PageQueryParam {
/**
* 字典项名称
*/
name: string | undefined;
/**
* 字典编码
*/
dictCode: string | undefined;
}
/**
* 字典分页列表项声明
*/
export interface DictItem {
id: number;
name: string;
value: string;
dictCode: string;
sort: number;
status: number;
defaulted: number;
remark?: string;
}
/**
* 字典分页项类型声明
*/
export type DictItemPageResult = PageResult<DictItem[]>
/**
* 字典表单类型声明
*/
export interface DictItemFormData {
id?: number;
dictCode?:string,
dictName?:string;
name: string;
code: string;
value: string;
status: number;
sort: number;
remark: string;
}

View File

@@ -1,27 +0,0 @@
/**
* 登录表单类型声明
*/
export interface LoginFormData {
username: string,
password: string,
grant_type: string,
code: string,
uuid: string,
}
/**
* 登录响应类型声明
*/
export interface LoginResponseData {
access_token: string,
token_type: string
}
/**
* 验证码类型声明
*/
export interface Captcha {
img: string,
uuid: string
}

View File

@@ -1,63 +0,0 @@
/**
* 菜单查询参数类型声明
*/
export interface MenuQueryParam {
name?: string
}
/**
* 菜单分页列表项声明
*/
export interface MenuItem {
id: number;
parentId: number;
gmtCreate: string;
gmtModified: string;
name: string;
icon: string;
component: string;
sort: number;
visible: number;
children: MenuItem[];
}
/**
* 菜单表单类型声明
*/
export interface MenuFormData {
/**
* 菜单ID
*/
id?: string ,
/**
* 父菜单ID
*/
parentId: string,
/**
* 菜单名称
*/
name: string,
/**
* 菜单是否可见(1:是;0:否;)
*/
visible: number,
icon: string,
/**
* 排序
*/
sort: number,
/**
* 组件路径
*/
component?: string,
/**
* 路由路径
*/
path: string,
/**
* 跳转路由路径
*/
redirect: string,
}

View File

@@ -1,37 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 权限查询参数类型声明
*/
export interface PermQueryParam extends PageQueryParam {
menuId: any;
name: string | undefined;
}
/**
* 权限分页列表项声明
*/
export interface PermItem {
id: number;
name: string;
menuId: string;
urlPerm: string;
btnPerm: string;
roles?: string[];
}
/**
* 权限分页项类型声明
*/
export type PermPageResult = PageResult<PermItem[]>
/**
* 权限表单类型声明
*/
export interface PermFormData {
id: number|undefined,
name: string,
urlPerm: string,
btnPerm: string,
menuId: string
}

View File

@@ -1,39 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 角色查询参数类型声明
*/
export interface RoleQueryParam extends PageQueryParam {
name?: string
}
/**
* 角色分页列表项声明
*/
export interface RoleItem {
id: string;
name: string;
code: string;
sort: number;
status: number;
deleted: number;
menuIds?: any;
permissionIds?: any;
}
/**
* 角色分页项类型声明
*/
export type RolePageResult = PageResult<RoleItem[]>
/**
* 角色表单类型声明
*/
export interface RoleFormData {
id: number|undefined,
name: string,
code: string,
sort: number,
status: number
}

View File

@@ -1,67 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 登录用户类型声明
*/
export interface UserInfo {
nickname: string,
avatar: string,
roles: string[],
perms: string[]
}
/**
* 用户查询参数类型声明
*/
export interface UserQueryParam extends PageQueryParam {
keywords: string,
status: number,
deptId: number
}
/**
* 用户分页列表项声明
*/
export interface UserItem {
id: string;
username: string;
nickname: string;
mobile: string;
gender: number;
avatar: string;
email: string;
status: number;
deptName: string;
roleNames: string;
gmtCreate: string;
}
/**
* 用户分页项类型声明
*/
export type UserPageResult = PageResult<UserItem[]>
/**
* 用户表单类型声明
*/
export interface UserFormData {
id: number | undefined,
deptId: number,
username: string,
nickname: string,
password: string,
mobile: string,
email: string,
gender: number,
status: number,
remark: string,
roleIds: number[]
}
/**
* 用户导入表单类型声明
*/
export interface UserImportFormData {
deptId: number,
roleIds: number[]
}

View File

@@ -1,64 +0,0 @@
import { PageQueryParam, PageResult } from "../base"
/**
* 会员查询参数类型声明
*/
export interface MemberQueryParam extends PageQueryParam {
nickName?: string
}
/**
* 会员分页列表项声明
*/
export interface MemberItem {
id: string;
gender: number;
nickName: string;
mobile: string;
birthday?: any;
avatarUrl: string;
openid: string;
sessionKey?: any;
city: string;
country: string;
language: string;
province: string;
status: number;
balance: string;
deleted: number;
point: number;
addressList: AddressItem[];
}
export interface AddressItem {
id: string;
memberId: string;
consigneeName: string;
consigneeMobile: string;
province: string;
city: string;
area: string;
detailAddress: string;
zipCode?: any;
defaulted: number;
}
/**
* 会员分页项类型声明
*/
export type MemberPageResult = PageResult<MemberItem[]>
/**
* 会员表单类型声明
*/
export interface MemberFormData {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

21
src/types/common.d.ts vendored
View File

@@ -1,21 +0,0 @@
/**
* 组件类型声明
*/
/**
* 弹窗属性类型声明
*/
export interface Dialog {
title: string,
visible: boolean
}
/**
* 通用组件选择项类型声明
*/
export interface Option {
value: string,
label: string
children?: Option[]
}

21
src/types/index.d.ts vendored
View File

@@ -1,21 +0,0 @@
export * from './api/system/login'
export * from './api/system/user'
export * from './api/system/role'
export * from './api/system/menu'
export * from './api/system/dept'
export * from './api/system/dict'
export * from './api/system/perm'
export * from './api/system/client'
export * from './api/pms/goods'
export * from './api/pms/brand'
export * from './api/sms/advert'
export * from './api/oms/order'
export * from './api/ums/member'
export * from './store'
export * from './common'

57
src/types/store.d.ts vendored
View File

@@ -1,57 +0,0 @@
import {RouteRecordRaw,RouteLocationNormalized} from "vue-router";
/**
* 用户状态类型声明
*/
export interface AppState {
device: string,
sidebar: {
opened: boolean,
withoutAnimation: boolean
},
language:string,
size:string
}
/**
* 权限类型声明
*/
export interface PermissionState{
routes:RouteRecordRaw[]
addRoutes: RouteRecordRaw[]
}
/**
* 设置状态类型声明
*/
export interface SettingState {
theme: string,
tagsView: boolean,
fixedHeader: boolean,
showSettings: boolean,
sidebarLogo: boolean
}
/**
* 标签状态类型声明
*/
export interface TagView extends Partial<RouteLocationNormalized> {
title?: string
}
export interface TagsViewState{
visitedViews: TagView[],
cachedViews: (string)[]
}
/**
* 用户状态类型声明
*/
export interface UserState {
token: string,
nickname: string,
avatar: string,
roles: string[],
perms: string[]
}

View File

@@ -0,0 +1,316 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "seata"
};
</script>
<script setup lang="ts">
import { reactive, onMounted, toRefs } from 'vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import {
Money,
Refresh,
RefreshLeft,
Right,
CircleCheckFilled,
CircleCloseFilled
} from "@element-plus/icons-vue";
import {
payOrder,
getSeataData,
resetSeataData
} from "@/api/lab/seata";
import { ElMessage, ElMessageBox } from 'element-plus';
import { SeataFormData } from "@/types";
const state = reactive({
// 保留改变前数据
cacheSeataData: {
status: undefined,
stockNum: undefined,
balance: undefined
},
seataData: {
orderInfo: {
orderSn: undefined,
status: undefined
},
skuInfo: {
name: undefined,
picUrl: undefined,
stockNum: undefined
},
memberInfo: {
nickName: undefined,
avatarUrl: undefined,
balance: undefined
}
},
loading: false,
submitData: {
openTx: true, // 是否开启事务
orderEx: true // 订单修改异常
} as SeataFormData
})
const { cacheSeataData, seataData, loading, submitData } = toRefs(state)
/**
* 订单支付(模拟)
*/
function handleOrderPay() {
// 数据校验
if (seataData.value.skuInfo.stockNum && seataData.value.skuInfo.stockNum != 999
|| (seataData.value.memberInfo.balance && seataData.value.memberInfo.balance != 1000000000)
|| (seataData.value.orderInfo.status && seataData.value.orderInfo.status != 101)
) {
ElMessageBox.confirm(
'检查到当前数据已被污染,请先重置数据后尝试提交?',
'警告',
{
confirmButtonText: '重置数据',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
handleDataReset()
}).catch(() => {
})
} else {
// 订单支付模拟提交
loading.value = true
payOrder(submitData.value).then(() => {
ElMessage.success('订单支付成功')
}).finally(() => {
cacheSeataData.value = {
status: seataData.value.orderInfo.status,
stockNum: seataData.value.skuInfo.stockNum,
balance: seataData.value.memberInfo.balance
}
loadData();
})
}
}
/**
* 加载数据
*/
function loadData() {
loading.value = true
getSeataData().then((response: any) => {
seataData.value = response.data
loading.value = false
})
}
/**
* 刷新数据
*/
function handleDataRefresh() {
loading.value = true
loadData();
}
/**
* 数据重置
*/
function handleDataReset() {
loading.value = true
resetSeataData().then(() => {
ElMessage.success('数据还原成功')
loading.value = false;
cacheSeataData.value = {
status: undefined,
stockNum: undefined,
balance: undefined
}
loadData();
})
}
onMounted(() => {
// 第一次加载重置数据测试
handleDataReset();
});
</script>
<template>
<div class="app-container">
<el-alert type="info">
<p style="font-size: 16px;">
<b>模拟订单支付流程</b>
扣减商品库存 扣减会员余额 修改订单状态
</p>
<p style="font-size: 14px;">
<b> 分布式事务生效判断</b>
<el-link :icon="CircleCheckFilled" type="success">全部成功</el-link>
<el-link type="danger" :icon="CircleCloseFilled">全部失败</el-link>
</p>
<p style="font-size: 14px;">
<b> 博客教程</b>
<el-link type="primary" href="https://www.cnblogs.com/haoxianrui/" target="_blank">
https://www.cnblogs.com/haoxianrui</el-link>
</p>
</el-alert>
<el-card class="box-card" shadow="always" style="margin-top:20px;">
<el-form :inline="true">
<el-row>
<el-col :span="20">
<el-form-item>
<el-switch v-model="submitData.openTx" active-value="" active-text="开启事务"
inactive-text="关闭事务" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Money" @click="handleOrderPay">订单支付</el-button>
<el-button :icon="Refresh" @click="handleDataRefresh">刷新数据</el-button>
</el-form-item>
</el-col>
<el-col :span="4" style="text-align: right;">
<el-button :icon="RefreshLeft" @click="handleDataReset">重置数据</el-button>
</el-col>
</el-row>
</el-form>
</el-card>
<el-row :gutter="10" style="margin-top:20px;" v-loading="loading">
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="goods" />
商品信息
</template>
<div style="display: flex;">
<el-image style="width: 100px; height: 100px" :src="seataData.skuInfo.picUrl" fit="fill" />
<div style="margin-left: 10px;">
<el-form-item label="商品名称:">
{{ seataData.skuInfo.name }}
</el-form-item>
<el-form-item label="库存数量:" style="display: flex;">
<div v-if="cacheSeataData.stockNum != null">
{{ cacheSeataData.stockNum }}
<el-icon>
<right />
</el-icon>
</div>
{{ seataData.skuInfo.stockNum }}
<div v-if="cacheSeataData.stockNum" style="margin-left: 50px;">
<el-link v-if="cacheSeataData.stockNum != seataData.skuInfo.stockNum" type="success"
:underline="false" :icon="CircleCheckFilled">
修改成功
</el-link>
<el-link v-else-if="cacheSeataData.stockNum == seataData.skuInfo.stockNum"
type="danger" :underline="false" :icon="CircleCloseFilled">
修改失败
</el-link>
</div>
</el-form-item>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="user" />
会员信息
</template>
<div style="display: flex;">
<el-image style="width: 100px; height: 100px" :src="seataData.memberInfo.avatarUrl"
fit="fill" />
<div style="margin-left: 10px;">
<el-form-item label="会员昵称:">
{{ seataData.memberInfo.nickName }}
</el-form-item>
<el-form-item label="会员余额:">
<div v-if="cacheSeataData.balance != null">
{{ (cacheSeataData.balance as any) / 100 }}
<el-icon>
<right />
</el-icon>
</div>
{{ (seataData.memberInfo.balance as any) / 100 }}
<div v-if="cacheSeataData.balance" style="margin-left: 50px;">
<el-link v-if="cacheSeataData.balance != seataData.memberInfo.balance"
type="success" :underline="false" :icon="CircleCheckFilled">
修改成功
</el-link>
<el-link v-else-if="cacheSeataData.balance == seataData.memberInfo.balance"
type="danger" :underline="false" :icon="CircleCloseFilled">
修改失败
</el-link>
</div>
</el-form-item>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="order" />
订单信息
<el-checkbox v-model="submitData.orderEx" :label="true" style="float: right;color: #F56C6C;">
搞点异常</el-checkbox>
</template>
<el-form-item label="订单编号:">
{{ seataData.orderInfo.orderSn }}
</el-form-item>
<el-form-item label="订单状态:">
<div v-if="cacheSeataData.status == 101">
<el-tag type="info">
待支付
</el-tag>
<el-icon>
<right />
</el-icon>
</div>
<el-tag v-if="seataData.orderInfo.status == 101" type="info">待支付</el-tag>
<el-tag v-else-if="seataData.orderInfo.status == 201" type="success">已支付</el-tag>
<div v-if="cacheSeataData.balance" style="margin-left: 50px;">
<el-link v-if="cacheSeataData.status != seataData.orderInfo.status" type="success"
:underline="false" :icon="CircleCheckFilled">
修改成功
</el-link>
<el-link v-else type="danger" :underline="false" :icon="CircleCloseFilled">
修改失败
</el-link>
</div>
</el-form-item>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style lang="scss" scoped>
.card-panel__col {
margin-bottom: 12px;
.el-link {
font-size: 16px;
margin-right: 8px;
}
}
</style>

View File

@@ -1,3 +1,122 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "order"
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from "vue";
import { ElForm } from "element-plus";
import { Dialog, Order, OrderQueryParam } from "@/types";
import { listOrderPages, getOrderDetail } from "@/api/oms/order";
import { Search, Refresh } from "@element-plus/icons-vue";
const queryFormRef = ref(ElForm);
const orderSourceMap = {
1: "微信小程序",
2: "APP",
3: "PC",
};
const orderStatusMap = {
101: "待付款",
102: "用户取消",
103: "系统取消",
201: "已付款",
202: "申请退款",
203: "已退款",
301: "待发货",
401: "已发货",
501: "用户收货",
502: "系统收货",
901: "已完成",
};
const payTypeMap = {
1: "支付宝",
2: "微信",
3: "会员余额",
};
const state = reactive({
loading: false,
ids: [],
single: true,
multiple: true,
dateRange: [],
queryParams: {
pageNum: 1,
pageSize: 10,
} as OrderQueryParam,
orderList: [] as Order[],
total: 0,
dialog: {
title: "订单详情",
visible: false,
} as Dialog,
dialogVisible: false,
orderDetail: {
order: {
refundAmount: undefined,
refundType: undefined,
refundNote: undefined,
gmtRefund: undefined,
confirmTime: undefined,
gmtDelivery: undefined,
shipSn: undefined,
shipChannel: undefined,
gmtPay: undefined,
integralPrice: undefined,
payChannel: undefined,
skuPrice: undefined,
couponPrice: undefined,
freightPrice: undefined,
orderPrice: undefined,
},
member: {},
orderItems: [],
},
orderSourceMap,
orderStatusMap,
payTypeMap,
});
const {
loading,
queryParams,
orderList,
total,
dateRange,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listOrderPages(state.queryParams).then(({ data }) => {
state.orderList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function viewDetail(row: any) {
state.dialog.visible = true;
getOrderDetail(row.id).then((response: any) => {
state.orderDetail = response.data;
});
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
@@ -116,118 +235,5 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from "vue";
import { ElForm } from "element-plus";
import { Dialog, Order, OrderQueryParam } from "@/types";
import { listOrderPages, getOrderDetail } from "@/api/oms/order";
import { Search, Refresh } from "@element-plus/icons-vue";
const queryFormRef = ref(ElForm);
const orderSourceMap = {
1: "微信小程序",
2: "APP",
3: "PC",
};
const orderStatusMap = {
101: "待付款",
102: "用户取消",
103: "系统取消",
201: "已付款",
202: "申请退款",
203: "已退款",
301: "待发货",
401: "已发货",
501: "用户收货",
502: "系统收货",
901: "已完成",
};
const payTypeMap = {
1: "支付宝",
2: "微信",
3: "会员余额",
};
const state = reactive({
loading: false,
ids: [],
single: true,
multiple: true,
dateRange: [],
queryParams: {
pageNum: 1,
pageSize: 10,
} as OrderQueryParam,
orderList: [] as Order[],
total: 0,
dialog: {
title: "订单详情",
visible: false,
} as Dialog,
dialogVisible: false,
orderDetail: {
order: {
refundAmount: undefined,
refundType: undefined,
refundNote: undefined,
gmtRefund: undefined,
confirmTime: undefined,
gmtDelivery: undefined,
shipSn: undefined,
shipChannel: undefined,
gmtPay: undefined,
integralPrice: undefined,
payChannel: undefined,
skuPrice: undefined,
couponPrice: undefined,
freightPrice: undefined,
orderPrice: undefined,
},
member: {},
orderItems: [],
},
orderSourceMap,
orderStatusMap,
payTypeMap,
});
const {
loading,
queryParams,
orderList,
total,
dateRange,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listOrderPages(state.queryParams).then(({ data }) => {
state.orderList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function viewDetail(row: any) {
state.dialog.visible = true;
getOrderDetail(row.id).then((response: any) => {
state.orderDetail = response.data;
});
}
onMounted(() => {
handleQuery();
});
</script>
<style scoped>
</style>

View File

@@ -1,76 +1,10 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" click="handleDelete" :disabled="multiple">删除</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="品牌名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="brandList" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" min-width="5%" />
<el-table-column prop="name" label="品牌名称" min-width="10" />
<el-table-column prop="logoUrl" label="LOGO" min-width="10">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.logoUrl" width="400" height="400" />
<template #reference>
<img :src="scope.row.logoUrl" style="max-height: 60px; max-width: 60px" />
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" min-width="10" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button @click="handleUpdate(scope.row)" type="primary" :icon="Edit" circle plain />
<el-button type="danger" :icon="Delete" circle plain @click="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" top="5vh" width="600px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="品牌名称" prop="name">
<el-input v-model="formData.name" auto-complete="off" />
</el-form-item>
<el-form-item label="LOGO" prop="logoUrl">
<single-upload v-model="formData.logoUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "brand"
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from "vue";
@@ -220,5 +154,79 @@ onMounted(() => {
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" click="handleDelete" :disabled="multiple">删除</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="品牌名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="brandList" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" min-width="5%" />
<el-table-column prop="name" label="品牌名称" min-width="10" />
<el-table-column prop="logoUrl" label="LOGO" min-width="10">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.logoUrl" width="400" height="400" />
<template #reference>
<img :src="scope.row.logoUrl" style="max-height: 60px; max-width: 60px" />
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" min-width="10" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button @click="handleUpdate(scope.row)" type="primary" :icon="Edit" circle plain />
<el-button type="danger" :icon="Delete" circle plain @click="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" top="5vh" width="600px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="品牌名称" prop="name">
<el-input v-model="formData.name" auto-complete="off" />
</el-form-item>
<el-form-item label="LOGO" prop="logoUrl">
<single-upload v-model="formData.logoUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,34 +1,11 @@
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="14" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu"/>
商品分类
</template>
<Category ref="categoryRef" @categoryClick="handleCategoryClick"/>
</el-card>
</el-col>
<el-col :span="10" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu"/>
{{category.name}} 规格属性
</template>
<!-- 商品规格 -->
<Attribute ref="specificationRef" :attributeType="1" :category="category"/>
<!-- 商品属性 -->
<Attribute ref="attributeRef" :attributeType="2" :category="category"/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "category"
};
</script>
<script setup lang="ts">
import Category from './components/Category.vue'
import Attribute from './components/Attribute.vue'
import SvgIcon from '@/components/SvgIcon/index.vue';
@@ -62,6 +39,35 @@ function handleCategoryClick(categoryRow: any) {
}
</script>
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="14" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu"/>
商品分类
</template>
<Category ref="categoryRef" @categoryClick="handleCategoryClick"/>
</el-card>
</el-col>
<el-col :span="10" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu"/>
{{category.name}} 规格属性
</template>
<!-- 商品规格 -->
<Attribute ref="specificationRef" :attributeType="1" :category="category"/>
<!-- 商品属性 -->
<Attribute ref="attributeRef" :attributeType="2" :category="category"/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped>
</style>

View File

@@ -146,13 +146,13 @@ function handleNext() {
.filter((item) => item.main == true && item.url)
.map((item) => item.url);
if (mainPicUrl && mainPicUrl.length > 0) {
goodsInfo.picUrl = mainPicUrl[0];
goodsInfo.value.picUrl = mainPicUrl[0];
}
const subPicUrl = state.pictures
.filter((item) => item.main == false && item.url)
.map((item) => item.url);
if (subPicUrl && subPicUrl.length > 0) {
goodsInfo.subPicUrls = subPicUrl;
goodsInfo.value.subPicUrls = subPicUrl;
}
emit("next");
}

View File

@@ -203,7 +203,6 @@ watch(() => goodsInfo.value.categoryId, (newVal) => {
});
}
},
{
immediate: true,

View File

@@ -33,10 +33,10 @@ const state = reactive({
loaded: false,
active: 0,
goodsInfo: {
album: [] as string[],
attrList: [] as any[],
specList: [] as any[],
skuList: [] as any[]
album: [],
attrList: [],
specList: [],
skuList: []
} as GoodsDetail
});
@@ -48,8 +48,8 @@ function loadData() {
if (goodsId) {
getGoodsDetail(goodsId).then((response) => {
state.goodsInfo = response.data
state.goodsInfo.originPrice = state.goodsInfo.originPrice / 100
state.goodsInfo.price = state.goodsInfo.price / 100
state.goodsInfo.originPrice = (state.goodsInfo.originPrice as any) / 100
state.goodsInfo.price = (state.goodsInfo.price as any) / 100
state.loaded = true
})
} else {

View File

@@ -1,8 +1,129 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "goods"
};
</script>
<script setup lang="ts">
import { reactive, ref, onMounted, toRefs } from "vue";
import { ElTable, ElMessage, ElMessageBox } from "element-plus";
import { useRouter } from "vue-router";
import { Search, Position, Edit, Refresh, Delete,View } from "@element-plus/icons-vue";
import { listGoodsPages, deleteGoods } from "@/api/pms/goods";
import { listCascadeCategories } from "@/api/pms/category";
import { GoodsItem, GoodsQueryParam } from "@/types";
import {moneyFormatter} from '@/utils/filter'
const dataTableRef = ref(ElTable);
const router = useRouter();
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
} as GoodsQueryParam,
goodsList: [] as GoodsItem[],
categoryOptions: [],
goodDetail: undefined,
dialogVisible: false,
});
const {
loading,
multiple,
queryParams,
goodsList,
categoryOptions,
goodDetail,
total,
dialogVisible,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listGoodsPages(state.queryParams).then(({ data }) => {
state.goodsList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
name: undefined,
categoryId: undefined,
};
handleQuery();
}
function handleGoodsView(detail: any) {
state.goodDetail = detail;
state.dialogVisible = true;
}
function handleAdd() {
router.push({ path: "goods-detail" });
}
function handleUpdate(row: any) {
router.push({
path: "goods-detail",
query: { goodsId: row.id, categoryId: row.categoryId },
});
}
function handleDelete(row: any) {
const ids = row.id || state.ids.join(",");
ElMessageBox.confirm("是否确认删除选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(function () {
return deleteGoods(ids);
})
.then(() => {
ElMessage.success("删除成功");
handleQuery();
});
}
function handleRowClick(row: any) {
dataTableRef.value.toggleRowSelection(row);
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
listCascadeCategories({}).then((response) => {
state.categoryOptions = ref(response.data);
});
handleQuery();
});
</script>
<template>
<div class="app-container">
<el-form ref="queryForm" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd"
<el-button type="success" :icon="Position" @click="handleAdd"
>发布商品</el-button
>
<el-button
@@ -141,119 +262,5 @@
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, toRefs } from "vue";
import { ElTable, ElMessage, ElMessageBox } from "element-plus";
import { useRouter } from "vue-router";
import { Search, Plus, Edit, Refresh, Delete,View } from "@element-plus/icons-vue";
import { listGoodsPages, deleteGoods } from "@/api/pms/goods";
import { listCascadeCategories } from "@/api/pms/category";
import { GoodsItem, GoodsQueryParam } from "@/types";
import {moneyFormatter} from '@/utils/filter'
const dataTableRef = ref(ElTable);
const router = useRouter();
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
} as GoodsQueryParam,
goodsList: [] as GoodsItem[],
categoryOptions: [],
goodDetail: undefined,
dialogVisible: false,
});
const {
loading,
multiple,
queryParams,
goodsList,
categoryOptions,
goodDetail,
total,
dialogVisible,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listGoodsPages(state.queryParams).then(({ data }) => {
state.goodsList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
name: undefined,
categoryId: undefined,
};
handleQuery();
}
function handleGoodsView(detail: any) {
state.goodDetail = detail;
state.dialogVisible = true;
}
function handleAdd() {
router.push({ path: "goods-detail" });
}
function handleUpdate(row: any) {
router.push({
path: "goods-detail",
query: { goodsId: row.id, categoryId: row.categoryId },
});
}
function handleDelete(row: any) {
const ids = row.id || state.ids.join(",");
ElMessageBox.confirm("是否确认删除选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(function () {
return deleteGoods(ids);
})
.then(() => {
ElMessage.success("删除成功");
handleQuery();
});
}
function handleRowClick(row: any) {
dataTableRef.value.toggleRowSelection(row);
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
listCascadeCategories({}).then((response) => {
state.categoryOptions = ref(response.data);
});
handleQuery();
});
</script>
<style scoped>
</style>

View File

@@ -1,102 +1,9 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-form-item>
<el-form-item prop="title">
<el-input v-model="queryParams.title" placeholder="广告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="advertList" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" min-width="5" align="center" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="title" min-width="100" label="广告标题" />
<el-table-column label="广告图片" width="100">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.picUrl" width="400" height="400" />
<template #reference>
<img :src="scope.row.picUrl" style="max-height: 60px; max-width: 60px" />
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="beginTime" label="开始时间" width="150" />
<el-table-column prop="endTime" label="结束时间" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">开启</el-tag>
<el-tag v-else type="info">关闭</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)" />
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="700px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="广告标题" prop="title">
<el-input v-model="formData.title" />
</el-form-item>
<el-form-item label="有效期" prop="beginTime">
<el-date-picker v-model="formData.beginTime" placeholder="开始时间" value-format="YYYY-MM-DD" />
~
<el-date-picker v-model="formData.endTime" placeholder="结束时间" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="广告图片" prop="picUrl">
<single-upload v-model="formData.picUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" style="width: 200px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="跳转链接" prop="url">
<el-input v-model="formData.url" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "advert"
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from "vue";
@@ -235,3 +142,103 @@ onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-form-item>
<el-form-item prop="title">
<el-input v-model="queryParams.title" placeholder="广告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="advertList" @selection-change="handleSelectionChange" border>
<el-table-column type="selection" min-width="5" align="center" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="title" min-width="100" label="广告标题" />
<el-table-column label="广告图片" width="100">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.picUrl" width="400" height="400" />
<template #reference>
<img :src="scope.row.picUrl" style="max-height: 60px; max-width: 60px" />
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="beginTime" label="开始时间" width="150" />
<el-table-column prop="endTime" label="结束时间" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">开启</el-tag>
<el-tag v-else type="info">关闭</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)" />
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="700px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="广告标题" prop="title">
<el-input v-model="formData.title" />
</el-form-item>
<el-form-item label="有效期" prop="beginTime">
<el-date-picker v-model="formData.beginTime" placeholder="开始时间" value-format="YYYY-MM-DD" />
~
<el-date-picker v-model="formData.endTime" placeholder="结束时间" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="广告图片" prop="picUrl">
<single-upload v-model="formData.picUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" style="width: 200px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="跳转链接" prop="url">
<el-input v-model="formData.url" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@@ -1,3 +1,174 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "client"
};
</script>
<script setup lang="ts">
import {
listClientPages,
getClientFormDetial,
addClient,
updateClient,
deleteClients,
} from "@/api/system/client";
import { Search, Plus, Edit, Refresh, Delete } from "@element-plus/icons-vue";
import { onMounted, reactive, getCurrentInstance, ref, toRefs } from "vue";
import { ElForm, ElMessage, ElMessageBox } from "element-plus";
import { ClientFormData, ClientItem, ClientQueryParam, Option } from "@/types";
const { proxy }: any = getCurrentInstance();
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
} as ClientQueryParam,
clientList: [] as ClientItem[],
total: 0,
dialog: {
title: "",
visible: false,
type: "create",
},
formData: {} as ClientFormData,
rules: {
clientId: [
{ required: true, message: "客户端ID不能为空", trigger: "blur" },
],
},
authorizedGrantTypesOptions: [] as Option[],
checkedAuthorizedGrantTypes: [] as string[]
});
const {
loading,
ids,
multiple,
queryParams,
clientList,
total,
dialog,
formData,
rules,
authorizedGrantTypesOptions,
checkedAuthorizedGrantTypes,
} = toRefs(state);
function handleQuery() {
listClientPages(state.queryParams).then(({ data }) => {
state.clientList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.clientId);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
function handleAdd() {
proxy.$listDictsByCode("grant_type").then((response: any) => {
state.authorizedGrantTypesOptions = response.data;
});
state.dialog = {
title: "添加客户端",
visible: true,
type: "create",
};
}
function handleUpdate(row: any) {
state.dialog = {
title: "修改客户端",
visible: true,
type: "edit",
};
const clientId = row.clientId || ids;
proxy.$listDictsByCode("grant_type").then((res: any) => {
state.authorizedGrantTypesOptions = res.data;
getClientFormDetial(clientId).then(({ data }) => {
state.formData = data;
state.checkedAuthorizedGrantTypes = data.authorizedGrantTypes?.split(",");
});
});
}
function submitForm() {
dataFormRef.value.validate((isvalid: boolean) => {
if (isvalid) {
state.formData.authorizedGrantTypes =
state.checkedAuthorizedGrantTypes.join(",");
if (state.dialog.type == "edit") {
updateClient(state.formData.clientId, state.formData).then(
() => {
ElMessage.success("修改成功");
state.dialog.visible = false;
cancel();
handleQuery();
}
);
} else {
addClient(state.formData).then(() => {
ElMessage.success("新增成功");
cancel();
handleQuery();
});
}
}
});
}
function resetForm() {
dataFormRef.value.resetFields();
state.checkedAuthorizedGrantTypes = [];
}
function cancel() {
resetForm();
state.dialog.visible = false;
}
function handleDelete(row: any) {
const clientIds = [row.clientId || ids].join(",");
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
deleteClients(clientIds).then(() => {
ElMessage.success("删除成功");
handleQuery();
});
})
.catch(() => ElMessage.info("已取消删除"));
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
@@ -126,168 +297,4 @@
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {
listClientPages,
getClientFormDetial,
addClient,
updateClient,
deleteClients,
} from "@/api/system/client";
import { Search, Plus, Edit, Refresh, Delete } from "@element-plus/icons-vue";
import { onMounted, reactive, getCurrentInstance, ref, toRefs } from "vue";
import { ElForm, ElMessage, ElMessageBox } from "element-plus";
import { ClientFormData, ClientItem, ClientQueryParam, Option } from "@/types";
const { proxy }: any = getCurrentInstance();
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
} as ClientQueryParam,
clientList: [] as ClientItem[],
total: 0,
dialog: {
title: "",
visible: false,
type: "create",
},
formData: {} as ClientFormData,
rules: {
clientId: [
{ required: true, message: "客户端ID不能为空", trigger: "blur" },
],
},
authorizedGrantTypesOptions: [] as Option[],
checkedAuthorizedGrantTypes: [] as string[],
});
const {
loading,
ids,
multiple,
queryParams,
clientList,
total,
dialog,
formData,
rules,
authorizedGrantTypesOptions,
checkedAuthorizedGrantTypes,
} = toRefs(state);
function handleQuery() {
listClientPages(state.queryParams).then(({ data }) => {
state.clientList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.clientId);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
function handleAdd() {
proxy.$listDictsByCode("grant_type").then((response: any) => {
state.authorizedGrantTypesOptions = response.data;
});
state.dialog = {
title: "添加客户端",
visible: true,
type: "create",
};
}
function handleUpdate(row: any) {
state.dialog = {
title: "修改客户端",
visible: true,
type: "edit",
};
const clientId = row.clientId || ids;
proxy.$listDictsByCode("grant_type").then((res: any) => {
state.authorizedGrantTypesOptions = res.data;
getClientFormDetial(clientId).then(({ data }) => {
state.formData = data;
state.checkedAuthorizedGrantTypes = data.authorizedGrantTypes?.split(",");
});
});
}
function submitForm() {
dataFormRef.value.validate((isvalid: boolean) => {
if (isvalid) {
state.formData.authorizedGrantTypes =
state.checkedAuthorizedGrantTypes.join(",");
if (state.dialog.type == "edit") {
updateClient(state.formData.clientId, state.formData).then(
() => {
ElMessage.success("修改成功");
state.dialog.visible = false;
cancel();
handleQuery();
}
);
} else {
addClient(state.formData).then(() => {
ElMessage.success("新增成功");
cancel();
handleQuery();
});
}
}
});
}
function resetForm() {
dataFormRef.value.resetFields();
state.checkedAuthorizedGrantTypes = [];
}
function cancel() {
resetForm();
state.dialog.visible = false;
}
function handleDelete(row: any) {
const clientIds = [row.clientId || ids].join(",");
ElMessageBox.confirm("确认删除已选中的数据项?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
deleteClients(clientIds).then(() => {
ElMessage.success("删除成功");
handleQuery();
});
})
.catch(() => ElMessage.info("已取消删除"));
}
onMounted(() => {
handleQuery();
});
</script>
</template>

View File

@@ -1,85 +1,9 @@
<template>
<div class="app-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="single" @click="handleDelete">删除
</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="请输入部门名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option :value="1" label="正常" />
<el-option :value="0" label="禁用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button class="filter-item" type="primary" :icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button :icon="Refresh" @click="resetQuery"> 重置 </el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="deptList" row-key="id" default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="部门名称" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="显示排序" width="200" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)">
</el-button>
<el-button type="success" :icon="Plus" circle plain @click.stop="handleAdd(scope.row)">
</el-button>
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)">
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改部门对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" @closed="cancel">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select v-model="formData.parentId" placeholder="选择上级部门" :data="deptOptions" filterable check-strictly />
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" style="width: 100px" :min="0" />
</el-form-item>
<el-form-item label="部门状态">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "dept"
};
</script>
<script setup lang="ts">
// Vue依赖
@@ -92,7 +16,7 @@ import {
updateDept,
addDept,
listSelectDepartments,
listTableDepartments,
listTableDepartments
} from "@/api/system/dept";
// 组件依赖
@@ -195,7 +119,7 @@ async function loadDeptData() {
*/
function handleAdd(row: any) {
loadDeptData();
state.formData.id=undefined
state.formData.id = undefined
state.formData.parentId = row.id;
state.dialog = {
title: "添加部门",
@@ -278,3 +202,85 @@ onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="single" @click="handleDelete">删除
</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="请输入部门名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option :value="1" label="正常" />
<el-option :value="0" label="禁用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button class="filter-item" type="primary" :icon="Search" @click="handleQuery">
搜索
</el-button>
<el-button :icon="Refresh" @click="resetQuery"> 重置 </el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="deptList" row-key="id" default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="部门名称" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="显示排序" width="200" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)">
</el-button>
<el-button type="success" :icon="Plus" circle plain @click.stop="handleAdd(scope.row)">
</el-button>
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)">
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改部门对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" @closed="cancel">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select v-model="formData.parentId" placeholder="选择上级部门" :data="deptOptions" filterable check-strictly />
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" style="width: 100px" :min="0" />
</el-form-item>
<el-form-item label="部门状态">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@@ -1,3 +1,10 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "dict"
};
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->

View File

@@ -57,26 +57,30 @@
<el-input v-model="formData.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="是否外链">
<el-radio-group v-model="isExternalPath">
<el-radio :label="false"></el-radio>
<el-radio :label="true"></el-radio>
<el-form-item label="菜单类型">
<el-radio-group v-model="formData.type" @change="handleMenuTypeChange">
<el-radio label="MENU">菜单</el-radio>
<el-radio label="CATALOG">目录</el-radio>
<el-radio label="EXTLINK">外链</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="isExternalPath" label="外链地址" prop="path">
<!-- 路由路径(浏览器地址栏显示) -->
<el-form-item v-if="formData.type == 'EXTLINK'" label="外链地址" prop="path">
<el-input v-model="formData.path" placeholder="请输入外链完整路径" />
</el-form-item>
<el-form-item v-if="!isExternalPath" label="页面路径" prop="component">
<el-form-item v-else label="路由路径" prop="path">
<el-input v-if="formData.type == 'CATALOG'" v-model="formData.path" placeholder="/system (注意:目录以/开头)" />
<el-input v-else v-model="formData.path" placeholder="user" />
</el-form-item>
<!-- 组件页面完整路径 -->
<el-form-item v-if="formData.type == 'MENU'" label="组件路径" prop="component">
<el-input v-model="formData.component" placeholder="system/user/index" style="width: 95%">
<template v-if="formData.parentId != '0'" #prepend>src/views/</template>
<template v-if="formData.parentId != '0'" #append>.vue</template>
</el-input>
<el-tooltip effect="dark" content="请输入组件路径,如果是父组件填写 Layout 即可" placement="right">
<i class="el-icon-info" style="margin-left: 10px; color: darkseagreen"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="图标" prop="icon">
@@ -93,6 +97,10 @@
</el-popover>
</el-form-item>
<el-form-item label="跳转路由">
<el-input v-model="formData.redirect" placeholder="跳转路由路径" maxlength="50" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="formData.visible">
<el-radio :label="1">显示</el-radio>
@@ -104,9 +112,7 @@
<el-input-number v-model="formData.sort" style="width: 100px" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="跳转路径">
<el-input v-model="formData.redirect" placeholder="请输入跳转路径" maxlength="50" />
</el-form-item>
</el-form>
<template #footer>
@@ -125,7 +131,6 @@ import { reactive, ref, onMounted, toRefs } from "vue";
import { Search, Plus, Edit, Refresh, Delete } from "@element-plus/icons-vue";
import { ElForm, ElMessage, ElMessageBox, ElPopover } from "element-plus";
import { isExternal } from "@/utils/validate";
import {
Dialog,
Option,
@@ -164,20 +169,28 @@ const state = reactive({
dialog: { visible: false } as Dialog,
formData: {
parentId: "0",
name: '',
visible: 1,
sort: 1,
component: "Layout",
component: 'Layout',
type: 'MENU'
} as MenuFormData,
rules: {
parentId: [{ required: true, message: "请选择顶级菜单", trigger: "blur" }],
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
component: [{ required: true, message: "请输入页面路径", trigger: "blur" }],
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
path: [{ required: true, message: "请输入路由路径", trigger: "blur" }],
component: [{ required: true, message: "请输入组件完整路径", trigger: "blur" }]
},
menuOptions: [] as Option[],
currentRow: undefined,
isExternalPath: false,
// Icon选择器显示状态
iconSelectVisible: false
iconSelectVisible: false,
cacheData: {
menuType: '',
menuPath: ''
}
});
const {
@@ -188,8 +201,8 @@ const {
formData,
rules,
menuOptions,
isExternalPath,
iconSelectVisible
iconSelectVisible,
cacheData
} = toRefs(state);
/**
@@ -237,26 +250,29 @@ async function handleAdd(row: any) {
title: "添加菜单",
visible: true
};
if (row.id) {
// 行点击新增
if (row.id) { // 行点击新增
state.formData.parentId = row.id;
if (row.id == '0') {
state.formData.component = "Layout";
state.formData.type = 'CATALOG';
} else {
state.formData.component = undefined;
state.formData.type = 'MENU';
}
} else {
} else { // 工具栏新增
if (state.currentRow) {
// 工具栏新增
state.formData.parentId = (state.currentRow as any).id;
state.formData.component = undefined;
state.formData.type = 'MENU';
} else {
state.formData.parentId = "0";
state.formData.component = "Layout";
state.formData.type = 'CATALOG';
}
}
}
/**
* 修改弹窗
*/
async function handleUpdate(row: any) {
await loadMenuData();
state.dialog = {
@@ -266,13 +282,25 @@ async function handleUpdate(row: any) {
const id = row.id || state.ids;
getMenuDetail(id).then(({ data }) => {
state.formData = data;
// 判断是否外部链接
state.isExternalPath = isExternal(state.formData.path);
cacheData.value.menuType = data.type
cacheData.value.menuPath = data.path
});
}
/**
* 菜单表单提交
* 菜单类型change事件
*/
function handleMenuTypeChange(val: any) {
if (val !== cacheData.value.menuType) {
formData.value.path = ''
} else {
formData.value.path = cacheData.value.menuPath
}
}
/**
* 菜单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
@@ -326,7 +354,6 @@ function selected(name: string) {
state.iconSelectVisible = false;
}
onMounted(() => {
handleQuery();
});

View File

@@ -1,33 +1,23 @@
<template>
<div class="menu-container">
<el-row>
<el-col :span="12">
<el-button plain :icon="Switch" @click="toggleExpandAll">展开/折叠</el-button>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" :icon="Check" @click="handleSubmit">提交</el-button>
</el-col>
</el-row>
<el-tree class="menu-container__tree" ref="menuRef" v-if="refreshTree" :default-expanded-keys="expandedKeys" :default-expand-all="isExpandAll"
:data="menuOptions" show-checkbox node-key="value" empty-text="加载菜单中..." :check-strictly="checkStrictly"
highlight-current @node-click="handleNodeClick" />
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "cmenu"
};
</script>
<script setup lang="ts">
import { nextTick, onMounted, reactive, ref, toRefs, watch } from "vue";
import { listSelectMenus } from "@/api/system/menu";
import { listRoleMenuIds, updateRoleMenu } from "@/api/system/role";
import { ElTree, ElMessage } from "element-plus";
import { Switch, Check } from "@element-plus/icons-vue";
import { Switch, Position } from "@element-plus/icons-vue";
import { Option } from "@/types";
const emit = defineEmits(["menuClick"]);
const props = defineProps({
role: {
type: Object,
default: () => { }
default: () => {}
},
});
@@ -99,10 +89,25 @@ onMounted(() => {
});
</script>
<template>
<div class="app-container">
<el-row>
<el-col :span="12">
<el-button plain :icon="Switch" @click="toggleExpandAll">展开/折叠</el-button>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" :icon="Position" @click="handleSubmit">提交</el-button>
</el-col>
</el-row>
<el-tree class="menu-tree" ref="menuRef" v-if="refreshTree" :default-expanded-keys="expandedKeys"
:default-expand-all="isExpandAll" :data="menuOptions" show-checkbox node-key="value" empty-text="加载菜单中..."
:check-strictly="checkStrictly" highlight-current @node-click="handleNodeClick" />
</div>
</template>
<style lang="scss" scoped>
.menu-container {
&__tree{
margin-top: 10px;
}
.menu-tree {
margin-top: 10px;
}
</style>

View File

@@ -7,7 +7,7 @@
</el-checkbox>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" :icon="Check" @click="handleSubmit">提交</el-button>
<el-button type="primary" :icon="Position" @click="handleSubmit">提交</el-button>
</el-col>
</el-row>
@@ -36,7 +36,7 @@ import { onMounted, reactive, toRefs, watch } from "vue";
import { listPerms } from "@/api/system/perm";
import { listRolePerms, saveRolePerms } from "@/api/system/role";
import { ElMessage } from "element-plus";
import { Check } from "@element-plus/icons-vue";
import { Position } from "@element-plus/icons-vue";
import { PermQueryParam } from "@/types";
const props = defineProps({

View File

@@ -1,72 +1,9 @@
<template>
<div class="role-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table ref="dataTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"
@row-click="handleRowClick" highlight-current-row border>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="name" />
<el-table-column label="角色编码" prop="code" />
<el-table-column label="操作" align="center" width="120">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)" />
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" @close="cancel" width="450px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入角色编码" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" style="width: 100px" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "role"
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from "vue";
@@ -107,7 +44,7 @@ const state = reactive({
rules: {
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
code: [{ required: true, message: "请输入角色编码", trigger: "blur" }],
},
}
});
const {
@@ -215,8 +152,77 @@ onMounted(() => {
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table ref="dataTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"
@row-click="handleRowClick" highlight-current-row border>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="name" />
<el-table-column label="角色编码" prop="code" />
<el-table-column label="操作" align="center" width="120">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click.stop="handleUpdate(scope.row)" />
<el-button type="danger" :icon="Delete" circle plain @click.stop="handleDelete(scope.row)" />
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="handleQuery" />
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" @close="cancel" width="450px">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入角色编码" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" style="width: 100px" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
.role-container {
width: 100%;
}
</style>

View File

@@ -1,189 +1,9 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :span="4" :xs="24">
<el-card class="box-card">
<el-input v-model="deptName" placeholder="部门名称" clearable :prefix-icon="Search" style="margin-bottom: 20px" />
<el-tree ref="deptTreeRef" :data="deptOptions" :props="{ children: 'children', label: 'label', disabled: '' }"
:expand-on-click-node="false" :filter-node-method="filterDeptNode" default-expand-all
@node-click="handleDeptNodeClick"></el-tree>
</el-card>
</el-col>
<!-- 用户数据 -->
<el-col :span="20" :xs="24">
<el-card class="box-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-row>
<el-col :span="18" :xs="24" >
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd" v-hasPerm="['sys:user:add']">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete"
v-hasPerm="['sys:user:delete']">删除</el-button>
</el-form-item>
<el-form-item prop="keywords">
<el-input v-model="queryParams.keywords" placeholder="用户名/昵称/手机号" clearable style="width: 200px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="status">
<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 200px">
<el-option label="正常" value="1" />
<el-option label="停用" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24" style="text-align: right;">
<el-form-item>
<el-dropdown split-button style="margin-left: 12px;">
导入
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="Download" @click="handleDownloadTemplate">下载模板</el-dropdown-item>
<el-dropdown-item :icon="Top" @click="showImportDialog">导入数据</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button :icon="Download" style="margin-left: 12px;" @click="handleExport">导出</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column key="id" label="用户编号" align="center" prop="id" />
<el-table-column key="username" label="用户名" align="center" prop="username" />
<el-table-column label="用户昵称" align="center" prop="nickname" />
<el-table-column label="性别" align="center" prop="gender" />
<el-table-column label="部门" align="center" prop="deptName" />
<el-table-column label="手机号码" align="center" prop="mobile" width="120" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" :inactive-value="0" :active-value="1"
@change="handleStatusChange(scope.row)" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="gmtCreate" width="180"></el-table-column>
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click="handleUpdate(scope.row)"
v-hasPerm="['sys:user:edit']"></el-button>
<el-button type="danger" :icon="Delete" circle plain @click="handleDelete(scope.row)"
v-hasPerm="['sys:user:delete']"></el-button>
<el-button type="warning" :icon="Lock" circle plain @click="resetPassword(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="handleQuery" />
</el-card>
</el-col>
</el-row>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body @close="cancel">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input :readonly="!!formData.id" v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickname">
<el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
</el-form-item>
<el-form-item label="所属部门" prop="deptId">
<el-tree-select v-model="formData.deptId" placeholder="请选择所属部门" :data="deptOptions" filterable />
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户性别" prop="gender">
<el-select v-model="formData.gender" placeholder="请选择">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="formData.roleIds" multiple placeholder="请选择">
<el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog :title="importDialog.title" v-model="importDialog.visible" width="600px" append-to-body
@close="closeImportDialog">
<el-form ref="importFormRef" :model="importFormData" :rules="rules" label-width="80px">
<el-form-item label="所属部门" prop="deptId">
<el-tree-select v-model="formData.deptId" placeholder="请选择所属部门" :data="deptOptions" filterable />
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="importFormData.roleIds" multiple placeholder="请选择">
<el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="Excel">
<el-upload class="upload-demo" action="" drag :auto-upload="false"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:file-list="excelFilelist" :on-change="handleExcelChange" :limit="1">
<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 files </div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitImportForm"> </el-button>
<el-button @click="closeImportDialog"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "user"
};
</script>
<script setup lang='ts'>
// Vue依赖
@@ -348,10 +168,10 @@ function filterDeptNode(value: string, data: any) {
}
/**
* 部门树节点点击
* 部门树节点点击事件
*/
function handleDeptNodeClick(data: { [key: string]: any }) {
state.queryParams.deptId = data.id;
state.queryParams.deptId = data.value;
handleQuery();
}
@@ -572,7 +392,7 @@ async function showImportDialog() {
state.importDialog.visible = true
}
// Excel文件上传
function handleExcelChange(file: UploadFile) {
if (!/\.(xlsx|xls|XLSX|XLS)$/.test(file.name)) {
ElMessage.warning('上传Excel只能为xlsx、xls格式');
@@ -645,6 +465,192 @@ onMounted(() => {
loadData();
});
</script>
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :span="4" :xs="24">
<el-card class="box-card">
<el-input v-model="deptName" placeholder="部门名称" clearable :prefix-icon="Search" style="margin-bottom: 20px" />
<el-tree ref="deptTreeRef" :data="deptOptions" :props="{ children: 'children', label: 'label', disabled: '' }"
:expand-on-click-node="false" :filter-node-method="filterDeptNode" default-expand-all
@node-click="handleDeptNodeClick"></el-tree>
</el-card>
</el-col>
<!-- 用户数据 -->
<el-col :span="20" :xs="24">
<el-card class="box-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-row>
<el-col :span="18" :xs="24" >
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd" v-hasPerm="['sys:user:add']">新增</el-button>
<el-button type="danger" :icon="Delete" :disabled="multiple" @click="handleDelete"
v-hasPerm="['sys:user:delete']">删除</el-button>
</el-form-item>
<el-form-item prop="keywords">
<el-input v-model="queryParams.keywords" placeholder="用户名/昵称/手机号" clearable style="width: 200px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="status">
<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 200px">
<el-option label="正常" value="1" />
<el-option label="停用" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
<el-col :span="6" :xs="24" style="text-align: right;">
<el-form-item>
<el-dropdown split-button style="margin-left: 12px;">
导入
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="Download" @click="handleDownloadTemplate">下载模板</el-dropdown-item>
<el-dropdown-item :icon="Top" @click="showImportDialog">导入数据</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button :icon="Download" style="margin-left: 12px;" @click="handleExport">导出</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column key="id" label="用户编号" align="center" prop="id" />
<el-table-column key="username" label="用户名" align="center" prop="username" />
<el-table-column label="用户昵称" align="center" prop="nickname" />
<el-table-column label="性别" align="center" prop="gender" />
<el-table-column label="部门" align="center" prop="deptName" />
<el-table-column label="手机号码" align="center" prop="mobile" width="120" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" :inactive-value="0" :active-value="1"
@change="handleStatusChange(scope.row)" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="gmtCreate" width="180"></el-table-column>
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" :icon="Edit" circle plain @click="handleUpdate(scope.row)"
v-hasPerm="['sys:user:edit']"></el-button>
<el-button type="danger" :icon="Delete" circle plain @click="handleDelete(scope.row)"
v-hasPerm="['sys:user:delete']"></el-button>
<el-button type="warning" :icon="Lock" circle plain @click="resetPassword(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="handleQuery" />
</el-card>
</el-col>
</el-row>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body @close="cancel">
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input :readonly="!!formData.id" v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickname">
<el-input v-model="formData.nickname" placeholder="请输入用户昵称" />
</el-form-item>
<el-form-item label="所属部门" prop="deptId">
<el-tree-select v-model="formData.deptId" placeholder="请选择所属部门" :data="deptOptions" filterable check-strictly />
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户性别" prop="gender">
<el-select v-model="formData.gender" placeholder="请选择">
<el-option label="未知" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="formData.roleIds" multiple placeholder="请选择">
<el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog :title="importDialog.title" v-model="importDialog.visible" width="600px" append-to-body
@close="closeImportDialog">
<el-form ref="importFormRef" :model="importFormData" :rules="rules" label-width="80px">
<el-form-item label="部门" prop="deptId">
<el-tree-select v-model="formData.deptId" placeholder="请选择部门" :data="deptOptions" filterable check-strictly />
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="importFormData.roleIds" multiple placeholder="请选择">
<el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="Excel">
<el-upload class="upload-demo" action="" drag :auto-upload="false"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:file-list="excelFilelist" :on-change="handleExcelChange" :limit="1">
<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 files </div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitImportForm"> </el-button>
<el-button @click="closeImportDialog"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
</style>

View File

@@ -1,3 +1,67 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: "member"
};
</script>
<script setup lang="ts">
import { reactive, onMounted, toRefs } from "vue";
import { ElTable } from "element-plus";
import { Search, Refresh } from "@element-plus/icons-vue";
import { listMemebersPage } from "@/api/ums/member";
import { MemberQueryParam, MemberItem } from "@/types";
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10
} as MemberQueryParam,
memberList: [] as MemberItem[]
});
const { loading, queryParams, memberList, total } =
toRefs(state);
function handleQuery() {
state.loading = true;
listMemebersPage(state.queryParams).then(({ data }) => {
state.memberList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
nickName: ''
};
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
@@ -81,62 +145,5 @@
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted, toRefs } from "vue";
import { ElTable } from "element-plus";
import { Search, Refresh } from "@element-plus/icons-vue";
import { listMemeberPages } from "@/api/ums/member";
import { MemberQueryParam, MemberItem } from "@/types";
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10
} as MemberQueryParam,
memberList: [] as MemberItem[]
});
const { loading, queryParams, memberList, total } =
toRefs(state);
function handleQuery() {
state.loading = true;
listMemeberPages(state.queryParams).then(({ data }) => {
state.memberList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
nickName: ''
};
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
handleQuery();
});
</script>
<style scoped>
</style>