style: 全局代码格式化
Former-commit-id: bb50c8419b8fcdeb48c93fce9f399d901e8f5a52
This commit is contained in:
@@ -23,4 +23,4 @@ module.exports = {
|
||||
// subject 大小写不做校验
|
||||
'subject-case': [0]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
22
src/App.vue
22
src/App.vue
@@ -5,14 +5,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
|
||||
import useStore from "@/store";
|
||||
import useStore from '@/store';
|
||||
|
||||
// 导入 Element Plus 语言包
|
||||
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||
import en from "element-plus/es/locale/lang/en";
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
|
||||
const { app } = useStore();
|
||||
|
||||
@@ -23,12 +23,12 @@ const locale = ref();
|
||||
|
||||
watch(
|
||||
language,
|
||||
(value) => {
|
||||
locale.value = value == "en" ? en : zhCn;
|
||||
}, {
|
||||
value => {
|
||||
locale.value = value == 'en' ? en : zhCn;
|
||||
},
|
||||
{
|
||||
// 初始化立即执行
|
||||
immediate: true
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SeataFormData } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { SeataFormData } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
@@ -10,7 +10,7 @@ export function payOrder(data: SeataFormData) {
|
||||
url: '/youlai-lab/api/v1/seata/order/_pay',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ export function getSeataData() {
|
||||
return request({
|
||||
url: '/youlai-lab/api/v1/seata/data',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,5 +32,5 @@ export function resetSeataData() {
|
||||
return request({
|
||||
url: '/youlai-lab/api/v1/seata/data/_reset',
|
||||
method: 'put'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Captcha, LoginFormData, LoginResponseData } from "@/types";
|
||||
import request from "@/utils/request";
|
||||
import { AxiosPromise } from "axios";
|
||||
import { Captcha, LoginFormData, LoginResponseData } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 登录
|
||||
@@ -12,9 +12,9 @@ export function login(data: LoginFormData): AxiosPromise<LoginResponseData> {
|
||||
method: 'post',
|
||||
params: data,
|
||||
headers: {
|
||||
'Authorization': 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // 客户端信息Base64明文:mall-admin-web:123456
|
||||
Authorization: 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // 客户端信息Base64明文:mall-admin-web:123456
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,7 @@ export function logout() {
|
||||
return request({
|
||||
url: '/youlai-auth/oauth/logout',
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +32,7 @@ export function logout() {
|
||||
*/
|
||||
export function getCaptcha(): AxiosPromise<Captcha> {
|
||||
return request({
|
||||
url: '/captcha?t=' + (new Date()).getTime().toString(),
|
||||
url: '/captcha?t=' + new Date().getTime().toString(),
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
import { OrderPageResult, OrderQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { OrderPageResult, OrderQueryParam } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取订单分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listOrderPages(queryParams: OrderQueryParam): AxiosPromise<OrderPageResult> {
|
||||
export function listOrderPages(
|
||||
queryParams: OrderQueryParam
|
||||
): AxiosPromise<OrderPageResult> {
|
||||
return request({
|
||||
url: '/mall-oms/api/v1/orders',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*
|
||||
@@ -25,5 +26,5 @@ export function getOrderDetail(orderId: number){
|
||||
return request({
|
||||
url: '/mall-oms/api/v1/orders/' + orderId,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from '@/utils/request'
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取商品属性列表
|
||||
@@ -10,10 +10,9 @@ export function listAttributes(params:object) {
|
||||
url: '/mall-pms/api/v1/attributes',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量修改商品属性
|
||||
*
|
||||
@@ -24,7 +23,5 @@ export function saveAttributeBatch(data:object) {
|
||||
url: '/mall-pms/api/v1/attributes/batch',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { BrandFormData, BrandItem, BrandPageResult, BrandQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import {
|
||||
BrandFormData,
|
||||
BrandItem,
|
||||
BrandPageResult,
|
||||
BrandQueryParam
|
||||
} from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取品牌分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listBrandPages(queryParams: BrandQueryParam):AxiosPromise<BrandPageResult> {
|
||||
export function listBrandPages(
|
||||
queryParams: BrandQueryParam
|
||||
): AxiosPromise<BrandPageResult> {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/brands/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,12 +27,14 @@ export function listBrandPages(queryParams: BrandQueryParam):AxiosPromise<BrandP
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listBrands(queryParams?: BrandQueryParam):AxiosPromise<BrandItem[]> {
|
||||
export function listBrands(
|
||||
queryParams?: BrandQueryParam
|
||||
): AxiosPromise<BrandItem[]> {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/brands',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +46,7 @@ export function getBrandFormDetail(id: number):AxiosPromise<BrandFormData> {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/brands/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +59,7 @@ export function addBrand(data: BrandFormData) {
|
||||
url: '/mall-pms/api/v1/brands',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +73,7 @@ export function updateBrand(id:number, data:BrandFormData) {
|
||||
url: '/mall-pms/api/v1/brands/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,5 +85,5 @@ export function deleteBrands(ids: string) {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/brands/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from "@/utils/request";
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取商品分类列表
|
||||
@@ -10,7 +10,7 @@ export function listCategories(queryParams:object){
|
||||
url: '/mall-pms/api/v1/categories',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ export function listCascadeCategories(queryParams?:object) {
|
||||
url: '/mall-pms/api/v1/categories/cascade',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ export function getCategoryDetail(id:number){
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/categories/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ export function addCategory(data:object){
|
||||
url: '/mall-pms/api/v1/categories',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +62,7 @@ export function updateCategory(id:number, data:object) {
|
||||
url: '/mall-pms/api/v1/categories/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,7 @@ export function deleteCategories(ids:string) {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/categories/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,5 +88,5 @@ export function updateCategoryPart(id:number, data:object) {
|
||||
url: '/mall-pms/api/v1/categories/' + id,
|
||||
method: 'patch',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { GoodsDetail, GoodsPageResult, GoodsQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { GoodsDetail, GoodsPageResult, GoodsQueryParam } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取商品分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listGoodsPages(queryParams: GoodsQueryParam):AxiosPromise<GoodsPageResult> {
|
||||
export function listGoodsPages(
|
||||
queryParams: GoodsQueryParam
|
||||
): AxiosPromise<GoodsPageResult> {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/goods/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ export function getGoodsDetail(id: string):AxiosPromise<GoodsDetail> {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/goods/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ export function addGoods(data: object) {
|
||||
url: '/mall-pms/api/v1/goods',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +53,7 @@ export function updateGoods(id: number, data: object) {
|
||||
url: '/mall-pms/api/v1/goods/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,5 +65,5 @@ export function deleteGoods(ids: string) {
|
||||
return request({
|
||||
url: '/mall-pms/api/v1/goods/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { AdvertFormData, AdvertPageResult, AdvertQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { AdvertFormData, AdvertPageResult, AdvertQueryParam } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取广告分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listAdvertPages(queryParams: AdvertQueryParam):AxiosPromise<AdvertPageResult> {
|
||||
export function listAdvertPages(
|
||||
queryParams: AdvertQueryParam
|
||||
): AxiosPromise<AdvertPageResult> {
|
||||
return request({
|
||||
url: '/mall-sms/api/v1/adverts',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ export function getAdvertFormDetail(id:number):AxiosPromise<AdvertFormData> {
|
||||
return request({
|
||||
url: '/mall-sms/api/v1/adverts/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ export function addAdvert(data: AdvertFormData) {
|
||||
url: '/mall-sms/api/v1/adverts',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +53,7 @@ export function updateAdvert(id: number, data: AdvertFormData) {
|
||||
url: '/mall-sms/api/v1/adverts/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,5 +65,5 @@ export function deleteAdverts(ids:string) {
|
||||
return request({
|
||||
url: '/mall-sms/api/v1/adverts/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
import { ClientFormData, ClientPageResult, ClientQueryParam } from '@/types/api/system/client'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import {
|
||||
ClientFormData,
|
||||
ClientPageResult,
|
||||
ClientQueryParam
|
||||
} from '@/types/api/system/client';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
export function listClientPages(queryParams:ClientQueryParam):AxiosPromise<ClientPageResult> {
|
||||
export function listClientPages(
|
||||
queryParams: ClientQueryParam
|
||||
): AxiosPromise<ClientPageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/oauth-clients',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function getClientFormDetial(id: number): AxiosPromise<ClientFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/oauth-clients/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function addClient(data: ClientFormData) {
|
||||
@@ -22,7 +28,7 @@ export function addClient(data:ClientFormData) {
|
||||
url: '/youlai-admin/api/v1/oauth-clients',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function updateClient(id: string, data: ClientFormData) {
|
||||
@@ -30,14 +36,14 @@ export function updateClient(id:string, data:ClientFormData) {
|
||||
url: '/youlai-admin/api/v1/oauth-clients/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteClients(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/oauth-clients/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function updateClientPart(id: number, data: object) {
|
||||
@@ -45,5 +51,5 @@ export function updateClientPart(id:number, data:object) {
|
||||
url: '/youlai-admin/api/v1/oauth-clients/' + id,
|
||||
method: 'patch',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { DeptFormData, DeptItem, DeptQueryParam, Option } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { DeptFormData, DeptItem, DeptQueryParam, Option } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 部门树形表格
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listTableDepartments(queryParams?: DeptQueryParam): AxiosPromise<DeptItem[]> {
|
||||
export function listTableDepartments(
|
||||
queryParams?: DeptQueryParam
|
||||
): AxiosPromise<DeptItem[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/depts/table',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +24,7 @@ export function listSelectDepartments(): AxiosPromise<Option[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/depts/select',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,10 +36,9 @@ export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/depts/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
*
|
||||
@@ -48,7 +49,7 @@ export function addDept(data: DeptFormData) {
|
||||
url: '/youlai-admin/api/v1/depts',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +63,7 @@ export function updateDept(id: string, data: DeptFormData) {
|
||||
url: '/youlai-admin/api/v1/depts/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +74,6 @@ export function updateDept(id: string, data: DeptFormData) {
|
||||
export function deleteDept(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/depts/' + ids,
|
||||
method: 'delete',
|
||||
})
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import { DictFormData, DictItemFormData, DictItemPageResult, DictItemQueryParam, DictPageResult, DictQueryParam, Option } from "@/types";
|
||||
import request from "@/utils/request";
|
||||
import { AxiosPromise } from "axios";
|
||||
import {
|
||||
DictFormData,
|
||||
DictItemFormData,
|
||||
DictItemPageResult,
|
||||
DictItemQueryParam,
|
||||
DictPageResult,
|
||||
DictQueryParam,
|
||||
Option
|
||||
} from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取字典分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listDictPages(queryParams: DictQueryParam): AxiosPromise<DictPageResult> {
|
||||
export function listDictPages(
|
||||
queryParams: DictQueryParam
|
||||
): AxiosPromise<DictPageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,10 +34,9 @@ export function getDictFormDetail(id: number): AxiosPromise<DictFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新增字典
|
||||
*
|
||||
@@ -38,7 +47,7 @@ export function addDict(data: DictFormData) {
|
||||
url: '/youlai-admin/api/v2/dict',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +61,7 @@ export function updateDict(id: number, data: DictFormData) {
|
||||
url: '/youlai-admin/api/v2/dict/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +71,8 @@ export function updateDict(id: number, data: DictFormData) {
|
||||
export function deleteDict(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/' + ids,
|
||||
method: 'delete',
|
||||
})
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,15 +80,16 @@ export function deleteDict(ids: string) {
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listDictItemPages(queryParams: DictItemQueryParam): AxiosPromise<DictItemPageResult> {
|
||||
export function listDictItemPages(
|
||||
queryParams: DictItemQueryParam
|
||||
): AxiosPromise<DictItemPageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/items/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据字典编码获取字典项列表
|
||||
*
|
||||
@@ -90,7 +100,7 @@ export function listDictsByCode(dictCode: string): AxiosPromise<Option[]> {
|
||||
url: '/youlai-admin/api/v2/dict/items',
|
||||
method: 'get',
|
||||
params: { dictCode: dictCode }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,11 +112,9 @@ export function getDictItemDetail(id: number): AxiosPromise<DictItemFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/items/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 新增字典项
|
||||
*
|
||||
@@ -117,7 +125,7 @@ export function addDictItem(data: any) {
|
||||
url: '/youlai-admin/api/v2/dict/items',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +139,7 @@ export function updateDictItem(id: number, data: any) {
|
||||
url: '/youlai-admin/api/v2/dict/items/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,6 +149,6 @@ export function updateDictItem(id: number, data: any) {
|
||||
export function deleteDictItem(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v2/dict/items/' + ids,
|
||||
method: 'delete',
|
||||
})
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from '@/utils/request'
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
@@ -6,17 +6,16 @@ import request from '@/utils/request'
|
||||
* @param file
|
||||
*/
|
||||
export function uploadFile(file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request(
|
||||
{
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/files',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,5 +28,5 @@ export function deleteFile(path?: string) {
|
||||
url: '/youlai-admin/api/v1/files',
|
||||
method: 'delete',
|
||||
params: { path: path }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MenuFormData, MenuItem, MenuQueryParam, Option } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { MenuFormData, MenuItem, MenuQueryParam, Option } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取路由列表
|
||||
@@ -9,7 +9,7 @@ export function listRoutes() {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/menus/route',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,12 +17,14 @@ export function listRoutes() {
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listTableMenus(queryParams: MenuQueryParam): AxiosPromise<MenuItem[]> {
|
||||
export function listTableMenus(
|
||||
queryParams: MenuQueryParam
|
||||
): AxiosPromise<MenuItem[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/menus/table',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,10 +34,9 @@ export function listSelectMenus(): AxiosPromise<Option[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/menus/select',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜单详情
|
||||
* @param id
|
||||
@@ -44,7 +45,7 @@ export function getMenuDetail(id: number): AxiosPromise<MenuFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/menus/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ export function addMenu(data: MenuFormData) {
|
||||
url: '/youlai-admin/api/v1/menus',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +72,7 @@ export function updateMenu(id: string, data: MenuFormData) {
|
||||
url: '/youlai-admin/api/v1/menus/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,5 +84,5 @@ export function deleteMenus(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/menus/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { PermFormData, PermItem, PermPageResult, PermQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import {
|
||||
PermFormData,
|
||||
PermItem,
|
||||
PermPageResult,
|
||||
PermQueryParam
|
||||
} from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取权限分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listPermPages(queryParams: PermQueryParam): AxiosPromise<PermPageResult> {
|
||||
export function listPermPages(
|
||||
queryParams: PermQueryParam
|
||||
): AxiosPromise<PermPageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/permissions/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,12 +27,14 @@ export function listPermPages(queryParams: PermQueryParam): AxiosPromise<PermPag
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listPerms(queryParams: PermQueryParam): AxiosPromise<PermItem[]> {
|
||||
export function listPerms(
|
||||
queryParams: PermQueryParam
|
||||
): AxiosPromise<PermItem[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/permissions',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +46,7 @@ export function getPermFormDetail(id: number): AxiosPromise<PermFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/permissions/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +59,7 @@ export function addPerm(data: PermFormData) {
|
||||
url: '/youlai-admin/api/v1/permissions',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +73,7 @@ export function updatePerm(id: number, data: PermFormData) {
|
||||
url: '/youlai-admin/api/v1/permissions/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,6 +85,5 @@ export function deletePerms(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/permissions/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { RoleFormData, RoleItem, RolePageResult, RoleQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import {
|
||||
RoleFormData,
|
||||
RoleItem,
|
||||
RolePageResult,
|
||||
RoleQueryParam
|
||||
} from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取角色分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listRolePages(queryParams?: RoleQueryParam):AxiosPromise<RolePageResult> {
|
||||
export function listRolePages(
|
||||
queryParams?: RoleQueryParam
|
||||
): AxiosPromise<RolePageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,12 +27,14 @@ export function listRolePages(queryParams?: RoleQueryParam):AxiosPromise<RolePag
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listRoles(queryParams?: RoleQueryParam):AxiosPromise<RoleItem[]> {
|
||||
export function listRoles(
|
||||
queryParams?: RoleQueryParam
|
||||
): AxiosPromise<RoleItem[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +46,7 @@ export function getRoleFormDetail(id: number):AxiosPromise<RoleFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +59,7 @@ export function addRole(data: RoleFormData) {
|
||||
url: '/youlai-admin/api/v1/roles',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +73,7 @@ export function updateRole(id: number, data: RoleFormData) {
|
||||
url: '/youlai-admin/api/v1/roles/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,10 +85,9 @@ export function deleteRoles(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取角色的菜单列表
|
||||
*
|
||||
@@ -88,8 +96,8 @@ export function deleteRoles(ids: string) {
|
||||
export function listRoleMenuIds(roleId: number): AxiosPromise<number[]> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles/' + roleId + '/menu_ids',
|
||||
method: 'get',
|
||||
})
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +111,9 @@ export function updateRoleMenu(roleId: number, menuIds: Array<number>) {
|
||||
url: '/youlai-admin/api/v1/roles/' + roleId + '/menus',
|
||||
method: 'put',
|
||||
data: { menuIds: menuIds }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取角色的权限列表
|
||||
*
|
||||
@@ -117,7 +124,7 @@ export function listRolePerms(roleId: number, menuId: number) {
|
||||
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
|
||||
method: 'get',
|
||||
params: { menuId: menuId }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,10 +134,14 @@ export function listRolePerms(roleId: number, menuId: number) {
|
||||
* @param roleId
|
||||
* @param permIds
|
||||
*/
|
||||
export function saveRolePerms(roleId: number, menuId: number, permIds: Array<number>) {
|
||||
export function saveRolePerms(
|
||||
roleId: number,
|
||||
menuId: number,
|
||||
permIds: Array<number>
|
||||
) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/roles/' + roleId + '/permissions',
|
||||
method: 'put',
|
||||
data: { menuId: menuId, permIds: permIds }
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import request from "@/utils/request";
|
||||
import { AxiosPromise } from "axios";
|
||||
import { UserFormData, UserInfo, UserPageResult, UserQueryParam } from "@/types";
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {
|
||||
UserFormData,
|
||||
UserInfo,
|
||||
UserPageResult,
|
||||
UserQueryParam
|
||||
} from '@/types';
|
||||
|
||||
/**
|
||||
* 登录成功后获取用户信息(昵称、头像、权限集合和角色集合)
|
||||
@@ -9,7 +14,7 @@ export function getUserInfo(): AxiosPromise<UserInfo> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/me',
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,12 +22,14 @@ export function getUserInfo(): AxiosPromise<UserInfo> {
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listUsersPage(queryParams: UserQueryParam): AxiosPromise<UserPageResult> {
|
||||
export function listUsersPage(
|
||||
queryParams: UserQueryParam
|
||||
): AxiosPromise<UserPageResult> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/page',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +41,7 @@ export function getUserDetail(userId: number): AxiosPromise<UserFormData> {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/' + userId,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +54,7 @@ export function addUser(data: any) {
|
||||
url: '/youlai-admin/api/v1/users',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +68,7 @@ export function updateUser(id: number, data: UserFormData) {
|
||||
url: '/youlai-admin/api/v1/users/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +82,7 @@ export function updateUserPart(id: number, data: any) {
|
||||
url: '/youlai-admin/api/v1/users/' + id,
|
||||
method: 'patch',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,8 +93,8 @@ export function updateUserPart(id: number, data: any) {
|
||||
export function deleteUsers(ids: string) {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/' + ids,
|
||||
method: 'delete',
|
||||
})
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +106,8 @@ export function downloadTemplate() {
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/template',
|
||||
method: 'get',
|
||||
responseType: "arraybuffer"
|
||||
})
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,8 +121,8 @@ export function exportUser(queryParams: UserQueryParam) {
|
||||
url: '/youlai-admin/api/v1/users/_export',
|
||||
method: 'get',
|
||||
params: queryParams,
|
||||
responseType: "arraybuffer"
|
||||
})
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,17 +131,16 @@ export function exportUser(queryParams: UserQueryParam) {
|
||||
* @param file
|
||||
*/
|
||||
export function importUser(deptId: number, roleIds: string, file: File) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('deptId',deptId.toString())
|
||||
formData.append('roleIds',roleIds)
|
||||
return request(
|
||||
{
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('deptId', deptId.toString());
|
||||
formData.append('roleIds', roleIds);
|
||||
return request({
|
||||
url: '/youlai-admin/api/v1/users/_import',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import { MemberPageResult, MemberQueryParam } from '@/types'
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { MemberPageResult, MemberQueryParam } from '@/types';
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 获取会员分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
*/
|
||||
export function listMemebersPage(queryParams: MemberQueryParam): AxiosPromise<MemberPageResult> {
|
||||
export function listMemebersPage(
|
||||
queryParams: MemberQueryParam
|
||||
): AxiosPromise<MemberPageResult> {
|
||||
return request({
|
||||
url: '/mall-ums/api/v1/members',
|
||||
method: 'get',
|
||||
params: queryParams
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ export function getMemberDetail(id: number) {
|
||||
return request({
|
||||
url: '/mall-ums/api/v1/members/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ export function addMember(data: object) {
|
||||
url: '/mall-ums/api/v1/members',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,5 +53,5 @@ export function updateMember(id: number, data: object) {
|
||||
url: '/mall-ums/api/v1/members/' + id,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
});
|
||||
}
|
||||
6
src/components.d.ts
vendored
6
src/components.d.ts
vendored
@@ -1,9 +1,9 @@
|
||||
// 全局组件类型声明
|
||||
import Pagination from "@/components/Pagination/index.vue";
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
|
||||
declare module "@vue/runtime-core" {
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
Pagination: typeof Pagination;
|
||||
}
|
||||
}
|
||||
export {}
|
||||
export {};
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
<template>
|
||||
<el-breadcrumb
|
||||
class="app-breadcrumb"
|
||||
separator-class="el-icon-arrow-right"
|
||||
>
|
||||
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
:key="item.path"
|
||||
>
|
||||
<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
|
||||
<span
|
||||
v-if="item.redirect === 'noredirect' || index === breadcrumbs.length-1"
|
||||
v-if="
|
||||
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
|
||||
"
|
||||
class="no-redirect"
|
||||
>{{ generateTitle(item.meta.title) }}</span>
|
||||
>{{ generateTitle(item.meta.title) }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">
|
||||
{{ generateTitle(item.meta.title) }}
|
||||
</a>
|
||||
@@ -21,64 +18,73 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onBeforeMount, ref, watch} from 'vue'
|
||||
import {useRoute, RouteLocationMatched} from 'vue-router'
|
||||
import {compile} from 'path-to-regexp'
|
||||
import router from '@/router'
|
||||
import {generateTitle} from '@/utils/i18n'
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import { useRoute, RouteLocationMatched } from 'vue-router';
|
||||
import { compile } from 'path-to-regexp';
|
||||
import router from '@/router';
|
||||
import { generateTitle } from '@/utils/i18n';
|
||||
|
||||
const currentRoute = useRoute()
|
||||
const currentRoute = useRoute();
|
||||
const pathCompile = (path: string) => {
|
||||
const {params} = currentRoute
|
||||
const toPath = compile(path)
|
||||
return toPath(params)
|
||||
}
|
||||
const { params } = currentRoute;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>)
|
||||
const breadcrumbs = ref([] as Array<RouteLocationMatched>);
|
||||
|
||||
function getBreadcrumb() {
|
||||
let matched = currentRoute.matched.filter((item) => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
let matched = currentRoute.matched.filter(
|
||||
item => item.meta && item.meta.title
|
||||
);
|
||||
const first = matched[0];
|
||||
if (!isDashboard(first)) {
|
||||
matched = [{path: '/dashboard', meta: {title: 'dashboard'}} as any].concat(matched)
|
||||
matched = [
|
||||
{ path: '/dashboard', meta: { title: 'dashboard' } } as any
|
||||
].concat(matched);
|
||||
}
|
||||
breadcrumbs.value = matched.filter((item) => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false
|
||||
})
|
||||
breadcrumbs.value = matched.filter(item => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
||||
});
|
||||
}
|
||||
|
||||
function isDashboard(route: RouteLocationMatched) {
|
||||
const name = route && route.name
|
||||
const name = route && route.name;
|
||||
if (!name) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
return name.toString().trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
||||
return (
|
||||
name.toString().trim().toLocaleLowerCase() ===
|
||||
'Dashboard'.toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function handleLink(item: any) {
|
||||
const {redirect, path} = item
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect).catch((err) => {
|
||||
console.warn(err)
|
||||
})
|
||||
return
|
||||
router.push(redirect).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path)).catch((err) => {
|
||||
console.warn(err)
|
||||
})
|
||||
router.push(pathCompile(path)).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => currentRoute.path, (path) => {
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
path => {
|
||||
if (path.startsWith('/redirect/')) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
getBreadcrumb()
|
||||
})
|
||||
getBreadcrumb();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getBreadcrumb()
|
||||
})
|
||||
|
||||
getBreadcrumb();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
<template>
|
||||
<a href="https://github.com/hxrui" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<a
|
||||
href="https://github.com/hxrui"
|
||||
target="_blank"
|
||||
class="github-corner"
|
||||
aria-label="View source on Github"
|
||||
>
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
style="fill:#40c9c6; color:#fff;"
|
||||
style="fill: #40c9c6; color: #fff"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
|
||||
<path
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
fill="currentColor"
|
||||
style="transform-origin: 130px 106px;"
|
||||
style="transform-origin: 130px 106px"
|
||||
class="octo-arm"
|
||||
/>
|
||||
<path
|
||||
@@ -25,30 +30,30 @@
|
||||
|
||||
<style scoped>
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes octocat-wave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
transform: rotate(0);
|
||||
}
|
||||
20%,
|
||||
60% {
|
||||
transform: rotate(-25deg)
|
||||
transform: rotate(-25deg);
|
||||
}
|
||||
40%,
|
||||
80% {
|
||||
transform: rotate(10deg)
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.github-corner:hover .octo-arm {
|
||||
animation: none
|
||||
animation: none;
|
||||
}
|
||||
.github-corner .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div style="padding: 0 15px;" @click="toggleClick">
|
||||
<div style="padding: 0 15px" @click="toggleClick">
|
||||
<svg
|
||||
:class="{ 'is-active': isActive }"
|
||||
class="hamburger"
|
||||
@@ -8,7 +8,9 @@
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
<path
|
||||
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -24,10 +26,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
this.$emit('toggleClick');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<template>
|
||||
<div class="icon-select">
|
||||
<el-input v-model="iconName" clearable placeholder="请输入图标名称" @clear="filterIcons"
|
||||
@input="filterIcons">
|
||||
<el-input
|
||||
v-model="iconName"
|
||||
clearable
|
||||
placeholder="请输入图标名称"
|
||||
@clear="filterIcons"
|
||||
@input="filterIcons"
|
||||
>
|
||||
<template #suffix><i class="el-icon-search el-input__icon" /></template>
|
||||
</el-input>
|
||||
<div class="icon-select__list">
|
||||
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
|
||||
<svg-icon color="#999" :icon-class="item" style="height: 30px;width: 16px;margin-right: 5px" />
|
||||
<div
|
||||
v-for="(item, index) in iconList"
|
||||
:key="index"
|
||||
@click="selectedIcon(item)"
|
||||
>
|
||||
<svg-icon
|
||||
color="#999"
|
||||
:icon-class="item"
|
||||
style="height: 30px; width: 16px; margin-right: 5px"
|
||||
/>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -14,10 +27,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref } from 'vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const icons = [] as string[]
|
||||
const icons = [] as string[];
|
||||
const modules = import.meta.glob('../../assets/icons/*.svg');
|
||||
for (const path in modules) {
|
||||
const p = path.split('assets/icons/')[1].split('.svg')[0];
|
||||
@@ -30,28 +43,28 @@ const iconName = ref('');
|
||||
const emit = defineEmits(['selected']);
|
||||
|
||||
function filterIcons() {
|
||||
iconList.value = icons
|
||||
iconList.value = icons;
|
||||
if (iconName.value) {
|
||||
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
|
||||
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectedIcon(name: string) {
|
||||
emit('selected', name)
|
||||
document.body.click()
|
||||
emit('selected', name);
|
||||
document.body.click();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
iconName.value = ''
|
||||
iconList.value = icons
|
||||
iconName.value = '';
|
||||
iconList.value = icons;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.icon-select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
@@ -17,32 +17,30 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import useStore from '@/store';
|
||||
|
||||
import {computed} from "vue";
|
||||
import useStore from "@/store";
|
||||
const { app } = useStore();
|
||||
const language = computed(() => app.language);
|
||||
|
||||
const {app}=useStore()
|
||||
const language = computed(() => app.language)
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
const {locale} = useI18n()
|
||||
const { locale } = useI18n();
|
||||
|
||||
function handleSetLanguage(lang: string) {
|
||||
locale.value = lang
|
||||
app.setLanguage(lang)
|
||||
locale.value = lang;
|
||||
app.setLanguage(lang);
|
||||
if (lang == 'en') {
|
||||
ElMessage.success('Switch Language Successful!')
|
||||
ElMessage.success('Switch Language Successful!');
|
||||
} else {
|
||||
ElMessage.success('切换语言成功!')
|
||||
ElMessage.success('切换语言成功!');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.lang-select__icon {
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<template>
|
||||
<div :class="{ 'hidden': hidden }" class="pagination-container">
|
||||
<el-pagination :background="background" v-model:current-page="currentPage" v-model:page-size="pageSize"
|
||||
:layout="layout" :page-sizes="pageSizes" :total="total" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
<div :class="{ hidden: hidden }" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType } from "vue";
|
||||
import { scrollTo } from '@/utils/scroll-to'
|
||||
import { computed, PropType } from 'vue';
|
||||
import { scrollTo } from '@/utils/scroll-to';
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
@@ -27,7 +34,7 @@ const props = defineProps({
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
return [10, 20, 30, 50];
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
@@ -46,38 +53,38 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:page", "update:limit", "pagination"]);
|
||||
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
|
||||
|
||||
const currentPage = computed<number | undefined>({
|
||||
get: () => props.page,
|
||||
set: (value) => {
|
||||
emit('update:page', value)
|
||||
set: value => {
|
||||
emit('update:page', value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const pageSize = computed<number | undefined>({
|
||||
get() {
|
||||
return props.limit
|
||||
return props.limit;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:limit', val)
|
||||
emit('update:limit', val);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
emit('pagination', { page: currentPage, limit: val })
|
||||
emit('pagination', { page: currentPage, limit: val });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val
|
||||
emit('pagination', { page: val, limit: props.limit })
|
||||
currentPage.value = val;
|
||||
emit('pagination', { page: val, limit: props.limit });
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,9 +2,19 @@
|
||||
<div ref="rightPanel" :class="{ show: show }" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div class="handle-button" :style="{ 'top': buttonTop + 'px', 'background-color': theme }" @click="show = !show">
|
||||
<Close style="width: 1em; height: 1em;vertical-align: middle " v-show="show" />
|
||||
<Setting style="width:1em; height:1em;vertical-align: middle " v-show="!show" />
|
||||
<div
|
||||
class="handle-button"
|
||||
:style="{ top: buttonTop + 'px', 'background-color': theme }"
|
||||
@click="show = !show"
|
||||
>
|
||||
<Close
|
||||
style="width: 1em; height: 1em; vertical-align: middle"
|
||||
v-show="show"
|
||||
/>
|
||||
<Setting
|
||||
style="width: 1em; height: 1em; vertical-align: middle"
|
||||
v-show="!show"
|
||||
/>
|
||||
</div>
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
@@ -14,74 +24,73 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { addClass, removeClass } from '@/utils/index'
|
||||
import { addClass, removeClass } from '@/utils/index';
|
||||
|
||||
import useStore from "@/store";
|
||||
import useStore from '@/store';
|
||||
|
||||
// 图标依赖
|
||||
import { Close, Setting } from '@element-plus/icons-vue'
|
||||
import { ElColorPicker } from "element-plus";
|
||||
import { Close, Setting } from '@element-plus/icons-vue';
|
||||
import { ElColorPicker } from 'element-plus';
|
||||
|
||||
const { setting } = useStore()
|
||||
const { setting } = useStore();
|
||||
|
||||
const theme = computed(() => setting.theme)
|
||||
const show = ref(false)
|
||||
const theme = computed(() => setting.theme);
|
||||
const show = ref(false);
|
||||
|
||||
defineProps({
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
watch(show, (value) => {
|
||||
watch(show, value => {
|
||||
if (value) {
|
||||
addEventClick()
|
||||
addEventClick();
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel')
|
||||
addClass(document.body, 'showRightPanel');
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel')
|
||||
removeClass(document.body, 'showRightPanel');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function addEventClick() {
|
||||
window.addEventListener('click', closeSidebar)
|
||||
window.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
function closeSidebar(evt: any) {
|
||||
|
||||
// 主题选择点击不关闭
|
||||
let parent = evt.target.closest('.theme-picker-dropdown')
|
||||
let parent = evt.target.closest('.theme-picker-dropdown');
|
||||
if (parent) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
parent = evt.target.closest('.rightPanel')
|
||||
parent = evt.target.closest('.rightPanel');
|
||||
if (!parent) {
|
||||
show.value = false
|
||||
window.removeEventListener('click', closeSidebar)
|
||||
show.value = false;
|
||||
window.removeEventListener('click', closeSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
const rightPanel = ref(ElColorPicker)
|
||||
const rightPanel = ref(ElColorPicker);
|
||||
|
||||
function insertToBody() {
|
||||
const elx = rightPanel.value as any
|
||||
const body = document.querySelector('body') as any
|
||||
body.insertBefore(elx, body.firstChild)
|
||||
const elx = rightPanel.value as any;
|
||||
const body = document.querySelector('body') as any;
|
||||
body.insertBefore(elx, body.firstChild);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
insertToBody()
|
||||
})
|
||||
insertToBody();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const elx = rightPanel.value as any
|
||||
elx.remove()
|
||||
})
|
||||
const elx = rightPanel.value as any;
|
||||
elx.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -98,8 +107,8 @@ onBeforeUnmount(() => {
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
|
||||
background: rgba(0, 0, 0, .2);
|
||||
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@@ -110,15 +119,15 @@ onBeforeUnmount(() => {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
|
||||
transition: all .25s cubic-bezier(.7, .3, .1, 1);
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all .3s cubic-bezier(.7, .3, .1, 1);
|
||||
transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
|
||||
<svg-icon
|
||||
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
|
||||
@click="toggle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
@@ -5,8 +5,12 @@
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="(size || 'default') == item.value"
|
||||
:command="item.value">
|
||||
<el-dropdown-item
|
||||
v-for="item of sizeOptions"
|
||||
:key="item.value"
|
||||
:disabled="(size || 'default') == item.value"
|
||||
:command="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -15,28 +19,28 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ref, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import useStore from "@/store";
|
||||
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||
import useStore from '@/store';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const { app } = useStore();
|
||||
const size = computed(() => app.size);
|
||||
|
||||
const sizeOptions = ref([
|
||||
{ label: "默认", value: "default" },
|
||||
{ label: "大型", value: "large" },
|
||||
{ label: "小型", value: "small" },
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '大型', value: 'large' },
|
||||
{ label: '小型', value: 'small' }
|
||||
]);
|
||||
|
||||
function handleSetSize(size: string) {
|
||||
app.setSize(size);
|
||||
ElMessage.success("切换布局大小成功");
|
||||
ElMessage.success('切换布局大小成功');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.size-select__icon {
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon',
|
||||
default: 'icon'
|
||||
},
|
||||
iconClass: {
|
||||
type: String,
|
||||
@@ -20,7 +20,7 @@ const props=defineProps({
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
|
||||
</script>
|
||||
|
||||
@@ -1,41 +1,52 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d' ]"
|
||||
:predefine="[
|
||||
'#409EFF',
|
||||
'#1890ff',
|
||||
'#304156',
|
||||
'#212121',
|
||||
'#11a983',
|
||||
'#13c2c2',
|
||||
'#6959CD',
|
||||
'#f5222d'
|
||||
]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, watch} from "vue";
|
||||
import useStore from "@/store";
|
||||
import {localStorage} from "@/utils/storage";
|
||||
import { computed, watch } from 'vue';
|
||||
import useStore from '@/store';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
|
||||
// 参考连接:https://juejin.cn/post/7024025899813044232#heading-1
|
||||
import {mix} from "@/utils";
|
||||
import { mix } from '@/utils';
|
||||
// 白色混合色
|
||||
const mixWhite = "#ffffff";
|
||||
const mixWhite = '#ffffff';
|
||||
// 黑色混合色
|
||||
const mixBlack = "#000000";
|
||||
const mixBlack = '#000000';
|
||||
|
||||
const node = document.documentElement;
|
||||
|
||||
const {setting} =useStore()
|
||||
const theme = computed(() => setting.theme)
|
||||
const { setting } = useStore();
|
||||
const theme = computed(() => setting.theme);
|
||||
|
||||
watch(theme, (color: string) => {
|
||||
node.style.setProperty("--el-color-primary", color);
|
||||
localStorage.set("theme", color)
|
||||
node.style.setProperty('--el-color-primary', color);
|
||||
localStorage.set('theme', color);
|
||||
|
||||
for (let i = 1; i < 10; i += 1) {
|
||||
node.style.setProperty(`--el-color-primary-light-${i}`, mix(color, mixWhite, i * 0.1));
|
||||
node.style.setProperty(
|
||||
`--el-color-primary-light-${i}`,
|
||||
mix(color, mixWhite, i * 0.1)
|
||||
);
|
||||
}
|
||||
node.style.setProperty("--el-color-primary-dark", mix(color, mixBlack, 0.1));
|
||||
|
||||
localStorage.set("style", node.style.cssText);
|
||||
})
|
||||
node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1));
|
||||
|
||||
localStorage.set('style', node.style.cssText);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -28,30 +28,30 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { Plus, Close } from "@element-plus/icons-vue";
|
||||
import { computed } from 'vue';
|
||||
import { Plus, Close } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElMessage,
|
||||
ElUpload,
|
||||
UploadRawFile,
|
||||
UploadRequestOptions,
|
||||
} from "element-plus";
|
||||
import { uploadFile, deleteFile } from "@/api/system/file";
|
||||
UploadRequestOptions
|
||||
} from 'element-plus';
|
||||
import { uploadFile, deleteFile } from '@/api/system/file';
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 是否显示右上角的删除图片按钮
|
||||
*/
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const imgUrl = computed<string | undefined>({
|
||||
@@ -60,8 +60,8 @@ const imgUrl = computed<string | undefined>({
|
||||
},
|
||||
set(val) {
|
||||
// imgUrl改变时触发修改父组件绑定的v-model的值
|
||||
emit("update:modelValue", val);
|
||||
},
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -93,7 +93,7 @@ function handleBeforeUpload(file: UploadRawFile) {
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
|
||||
if (!isLt2M) {
|
||||
ElMessage.warning("上传图片不能大于2M");
|
||||
ElMessage.warning('上传图片不能大于2M');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
<template>
|
||||
<div style="border: 1px solid #ccc">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" style="border-bottom: 1px solid #ccc" :mode="mode" />
|
||||
<Toolbar
|
||||
:editor="editorRef"
|
||||
:defaultConfig="toolbarConfig"
|
||||
style="border-bottom: 1px solid #ccc"
|
||||
:mode="mode"
|
||||
/>
|
||||
<!-- 编辑器 -->
|
||||
<Editor :defaultConfig="editorConfig" v-model="defaultHtml" @onChange="handleChange"
|
||||
style="height: 500px; overflow-y: hidden;" :mode="mode" @onCreated="handleCreated" />
|
||||
<Editor
|
||||
:defaultConfig="editorConfig"
|
||||
v-model="defaultHtml"
|
||||
@onChange="handleChange"
|
||||
style="height: 500px; overflow-y: hidden"
|
||||
:mode="mode"
|
||||
@onCreated="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, shallowRef, reactive, toRefs} from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
||||
|
||||
// API 引用
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { uploadFile } from '@/api/system/file';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String],
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
const editorRef = shallowRef();
|
||||
|
||||
const state = reactive({
|
||||
toolbarConfig: {},
|
||||
@@ -36,35 +47,33 @@ const state = reactive({
|
||||
// 自定义图片上传
|
||||
async customUpload(file: any, insertFn: any) {
|
||||
uploadFile(file).then(response => {
|
||||
const url = response.data
|
||||
insertFn(url)
|
||||
})
|
||||
const url = response.data;
|
||||
insertFn(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultHtml: props.modelValue,
|
||||
mode: 'default'
|
||||
})
|
||||
});
|
||||
|
||||
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state)
|
||||
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);
|
||||
|
||||
const handleCreated = (editor: any) => {
|
||||
editorRef.value = editor // 记录 editor 实例,重要!
|
||||
}
|
||||
editorRef.value = editor; // 记录 editor 实例,重要!
|
||||
};
|
||||
|
||||
function handleChange(editor: any) {
|
||||
emit('update:modelValue', editor.getHtml())
|
||||
emit('update:modelValue', editor.getHtml());
|
||||
}
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
editor.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style src="@wangeditor/editor/dist/css/style.css">
|
||||
</style>
|
||||
<style src="@wangeditor/editor/dist/css/style.css"></style>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export {hasPerm,hasRole} from "./permission";
|
||||
export { hasPerm, hasRole } from './permission';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import useStore from "@/store";
|
||||
import { Directive, DirectiveBinding } from "vue";
|
||||
import useStore from '@/store';
|
||||
import { Directive, DirectiveBinding } from 'vue';
|
||||
|
||||
/**
|
||||
* 按钮权限校验
|
||||
@@ -7,10 +7,10 @@ import { Directive, DirectiveBinding } from "vue";
|
||||
export const hasPerm: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
// 「超级管理员」拥有所有的按钮权限
|
||||
const { user } = useStore()
|
||||
const { user } = useStore();
|
||||
const roles = user.roles;
|
||||
if (roles.includes('ROOT')) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
// 「其他角色」按钮权限校验
|
||||
const { value } = binding;
|
||||
@@ -18,14 +18,16 @@ export const hasPerm: Directive = {
|
||||
const requiredPerms = value; // DOM绑定需要的按钮权限标识
|
||||
|
||||
const hasPerm = user.perms?.some(perm => {
|
||||
return requiredPerms.includes(perm)
|
||||
})
|
||||
return requiredPerms.includes(perm);
|
||||
});
|
||||
|
||||
if (!hasPerm) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
}
|
||||
} else {
|
||||
throw new Error("need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"");
|
||||
throw new Error(
|
||||
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -39,10 +41,10 @@ export const hasRole: Directive = {
|
||||
|
||||
if (value) {
|
||||
const requiredRoles = value; // DOM绑定需要的角色编码
|
||||
const { user } = useStore()
|
||||
const { user } = useStore();
|
||||
const hasRole = user.roles.some(perm => {
|
||||
return requiredRoles.includes(perm)
|
||||
})
|
||||
return requiredRoles.includes(perm);
|
||||
});
|
||||
|
||||
if (!hasRole) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
@@ -52,4 +54,3 @@ export const hasRole: Directive = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
14
src/env.d.ts
vendored
14
src/env.d.ts
vendored
@@ -1,19 +1,19 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
import { DefineComponent } from 'vue';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// 环境变量 TypeScript的智能提示
|
||||
interface ImportMetaEnv {
|
||||
VITE_APP_TITLE: string,
|
||||
VITE_APP_PORT: string,
|
||||
VITE_APP_BASE_API: string
|
||||
VITE_APP_TITLE: string;
|
||||
VITE_APP_PORT: string;
|
||||
VITE_APP_BASE_API: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -21,4 +21,4 @@ export default {
|
||||
document: 'Document',
|
||||
gitee: 'Gitee'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
// 自定义国际化配置
|
||||
import {createI18n} from 'vue-i18n'
|
||||
import {localStorage} from '@/utils/storage'
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
|
||||
// 本地语言包
|
||||
import enLocale from './en'
|
||||
import zhCnLocale from './zh-cn'
|
||||
import enLocale from './en';
|
||||
import zhCnLocale from './zh-cn';
|
||||
|
||||
const messages = {
|
||||
'zh-cn': {
|
||||
@@ -14,7 +13,7 @@ const messages = {
|
||||
en: {
|
||||
...enLocale
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前系统使用语言字符串
|
||||
@@ -23,24 +22,24 @@ const messages = {
|
||||
*/
|
||||
export const getLanguage = () => {
|
||||
// 本地缓存获取
|
||||
let language = localStorage.get('language')
|
||||
let language = localStorage.get('language');
|
||||
if (language) {
|
||||
return language
|
||||
return language;
|
||||
}
|
||||
// 浏览器使用语言
|
||||
language = navigator.language.toLowerCase()
|
||||
const locales = Object.keys(messages)
|
||||
language = navigator.language.toLowerCase();
|
||||
const locales = Object.keys(messages);
|
||||
for (const locale of locales) {
|
||||
if (language.indexOf(locale) > -1) {
|
||||
return locale
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
return 'zh-cn'
|
||||
}
|
||||
return 'zh-cn';
|
||||
};
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: getLanguage(),
|
||||
messages: messages
|
||||
})
|
||||
});
|
||||
|
||||
export default i18n
|
||||
export default i18n;
|
||||
|
||||
@@ -20,4 +20,4 @@ export default {
|
||||
document: '项目文档',
|
||||
gitee: '码云'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import useStore from "@/store";
|
||||
import { computed } from 'vue';
|
||||
import useStore from '@/store';
|
||||
|
||||
const { tagsView } = useStore();
|
||||
|
||||
|
||||
@@ -32,19 +32,19 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<router-link to="/">
|
||||
<el-dropdown-item>{{ $t("navbar.dashboard") }}</el-dropdown-item>
|
||||
<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>
|
||||
<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>
|
||||
<el-dropdown-item>{{ $t('navbar.document') }}</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
{{ $t("navbar.logout") }}
|
||||
{{ $t('navbar.logout') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@@ -53,21 +53,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
import useStore from "@/store";
|
||||
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 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";
|
||||
import { CaretBottom } from '@element-plus/icons-vue';
|
||||
|
||||
const { app, user } = useStore();
|
||||
|
||||
@@ -83,10 +83,10 @@ function toggleSideBar() {
|
||||
}
|
||||
|
||||
function logout() {
|
||||
ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
user.logout().then(() => {
|
||||
router.push(`/login?redirect=${route.fullPath}`);
|
||||
|
||||
@@ -26,44 +26,44 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRefs, watch } from "vue";
|
||||
import { reactive, toRefs, watch } from 'vue';
|
||||
|
||||
import ThemePicker from "@/components/ThemePicker/index.vue";
|
||||
import ThemePicker from '@/components/ThemePicker/index.vue';
|
||||
|
||||
import useStore from "@/store";
|
||||
import useStore from '@/store';
|
||||
|
||||
const { setting } = useStore();
|
||||
|
||||
const state = reactive({
|
||||
fixedHeader: setting.fixedHeader,
|
||||
tagsView: setting.tagsView,
|
||||
sidebarLogo: setting.sidebarLogo,
|
||||
sidebarLogo: setting.sidebarLogo
|
||||
});
|
||||
|
||||
const { fixedHeader, tagsView, sidebarLogo } = toRefs(state);
|
||||
|
||||
function themeChange(val: any) {
|
||||
setting.changeSetting({ key: "theme", value: val });
|
||||
setting.changeSetting({ key: 'theme', value: val });
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.fixedHeader,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "fixedHeader", value: value });
|
||||
value => {
|
||||
setting.changeSetting({ key: 'fixedHeader', value: value });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.tagsView,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "tagsView", value: value });
|
||||
value => {
|
||||
setting.changeSetting({ key: 'tagsView', value: value });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.sidebarLogo,
|
||||
(value) => {
|
||||
setting.changeSetting({ key: "sidebarLogo", value: value });
|
||||
value => {
|
||||
setting.changeSetting({ key: 'sidebarLogo', value: value });
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="isExternal(to)"
|
||||
:href="to"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
|
||||
<slot />
|
||||
</a>
|
||||
<div
|
||||
v-else
|
||||
@click="push"
|
||||
>
|
||||
<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 { computed, defineComponent } from 'vue';
|
||||
import { isExternal } from '@/utils/validate';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import useStore from "@/store";
|
||||
import useStore from '@/store';
|
||||
|
||||
const {app}=useStore()
|
||||
const { app } = useStore();
|
||||
|
||||
const sidebar = computed(() => app.sidebar);
|
||||
const device = computed(() => app.device);
|
||||
@@ -35,19 +27,19 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const router = useRouter()
|
||||
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)
|
||||
})
|
||||
app.closeSideBar(false);
|
||||
}
|
||||
router.push(props.to).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
return {
|
||||
push,
|
||||
isExternal
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -1,12 +1,17 @@
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{ 'collapse': isCollapse }">
|
||||
<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">
|
||||
<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">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||
<h1 class="sidebar-title">{{ title }}</h1>
|
||||
</router-link>
|
||||
</transition>
|
||||
@@ -21,17 +26,16 @@ const props = defineProps({
|
||||
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")
|
||||
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>
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<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)"
|
||||
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"/>
|
||||
<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>
|
||||
@@ -15,8 +25,13 @@
|
||||
<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>
|
||||
<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
|
||||
@@ -32,12 +47,12 @@
|
||||
</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 { ref } from 'vue';
|
||||
import path from 'path-browserify';
|
||||
import { isExternal } from '@/utils/validate';
|
||||
import AppLink from './Link.vue';
|
||||
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
import { generateTitle } from '@/utils/i18n';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -53,7 +68,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const onlyOneChild = ref();
|
||||
|
||||
@@ -63,40 +78,37 @@ function hasOneShowingChild(children = [] as any, parent: any) {
|
||||
}
|
||||
const showingChildren = children.filter((item: any) => {
|
||||
if (item.meta && item.meta.hidden) {
|
||||
return false
|
||||
return false;
|
||||
} else {
|
||||
// Temp set(will be used if only has one showing child)
|
||||
onlyOneChild.value = item
|
||||
return true
|
||||
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
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
onlyOneChild.value = {...parent, path: '', noShowingChildren: true}
|
||||
return true
|
||||
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolvePath(routePath: string) {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath
|
||||
return routePath;
|
||||
}
|
||||
if (isExternal(props.basePath)) {
|
||||
return props.basePath
|
||||
return props.basePath;
|
||||
}
|
||||
return path.resolve(props.basePath, routePath)
|
||||
return path.resolve(props.basePath, routePath);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
:active-text-color="variables.menuActiveText"
|
||||
:unique-opened="false"
|
||||
:collapse-transition="false"
|
||||
mode="vertical">
|
||||
mode="vertical"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="route in routes"
|
||||
:item="route"
|
||||
@@ -24,28 +25,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
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";
|
||||
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 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
|
||||
const { meta, path } = route;
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu as string
|
||||
return meta.activeMenu as string;
|
||||
}
|
||||
return path
|
||||
})
|
||||
return path;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -16,10 +16,9 @@ import {
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { TagView } from "@/types";
|
||||
import useStore from "@/store";
|
||||
|
||||
} from 'vue';
|
||||
import { TagView } from '@/types';
|
||||
import useStore from '@/store';
|
||||
|
||||
const tagAndTagSpacing = ref(4);
|
||||
const scrollContainerRef = ref(null);
|
||||
@@ -28,7 +27,6 @@ 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;
|
||||
@@ -67,14 +65,14 @@ function moveToTarget(currentTag: TagView) {
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
||||
} else {
|
||||
const tagListDom = document.getElementsByClassName("tags-view__item");
|
||||
const tagListDom = document.getElementsByClassName('tags-view__item');
|
||||
const currentIndex = visitedViews.value.findIndex(
|
||||
(item) => item === currentTag
|
||||
item => item === currentTag
|
||||
);
|
||||
let prevTag = null;
|
||||
let nextTag = null;
|
||||
for (const k in tagListDom) {
|
||||
if (k !== "length" && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||
if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||
if (
|
||||
(tagListDom[k] as any).dataset.path ===
|
||||
visitedViews.value[currentIndex - 1].path
|
||||
@@ -108,7 +106,7 @@ function moveToTarget(currentTag: TagView) {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
moveToTarget,
|
||||
moveToTarget
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
<template>
|
||||
<div 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="icon-close" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<span
|
||||
v-if="!isAffix(tag)"
|
||||
class="icon-close"
|
||||
@click.prevent.stop="closeSelectedTag(tag)"
|
||||
>
|
||||
<svg-icon icon-class="close" />
|
||||
</span>
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="tags-view__menu">
|
||||
<ul
|
||||
v-show="visible"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="tags-view__menu"
|
||||
>
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
<svg-icon icon-class="refresh" />
|
||||
刷新
|
||||
@@ -47,18 +65,18 @@ import {
|
||||
ref,
|
||||
watch,
|
||||
onMounted,
|
||||
ComponentInternalInstance,
|
||||
} from "vue";
|
||||
ComponentInternalInstance
|
||||
} from 'vue';
|
||||
|
||||
import path from "path-browserify";
|
||||
import path from 'path-browserify';
|
||||
|
||||
import { RouteRecordRaw, useRoute, useRouter } from "vue-router";
|
||||
import { TagView } from "@/types";
|
||||
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||
import { TagView } from '@/types';
|
||||
|
||||
import ScrollPane from "./ScrollPane.vue";
|
||||
import ScrollPane from './ScrollPane.vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { generateTitle } from "@/utils/i18n";
|
||||
import useStore from "@/store";
|
||||
import { generateTitle } from '@/utils/i18n';
|
||||
import useStore from '@/store';
|
||||
|
||||
const { tagsView, permission } = useStore();
|
||||
|
||||
@@ -81,25 +99,25 @@ watch(route, () => {
|
||||
moveToCurrentTag();
|
||||
});
|
||||
|
||||
watch(visible, (value) => {
|
||||
watch(visible, value => {
|
||||
if (value) {
|
||||
document.body.addEventListener("click", closeMenu);
|
||||
document.body.addEventListener('click', closeMenu);
|
||||
} else {
|
||||
document.body.removeEventListener("click", closeMenu);
|
||||
document.body.removeEventListener('click', closeMenu);
|
||||
}
|
||||
});
|
||||
|
||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
||||
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/') {
|
||||
let tags: TagView[] = [];
|
||||
|
||||
routes.forEach((route) => {
|
||||
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 },
|
||||
meta: { ...route.meta }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,7 +179,7 @@ function isFirstView() {
|
||||
return (
|
||||
(selectedTag.value as TagView).fullPath ===
|
||||
visitedViews.value[1].fullPath ||
|
||||
(selectedTag.value as TagView).fullPath === "/index"
|
||||
(selectedTag.value as TagView).fullPath === '/index'
|
||||
);
|
||||
} catch (err) {
|
||||
return false;
|
||||
@@ -183,7 +201,7 @@ function refreshSelectedTag(view: TagView) {
|
||||
tagsView.delCachedView(view);
|
||||
const { fullPath } = view;
|
||||
nextTick(() => {
|
||||
router.replace({ path: "/redirect" + fullPath }).catch((err) => {
|
||||
router.replace({ path: '/redirect' + fullPath }).catch(err => {
|
||||
console.warn(err);
|
||||
});
|
||||
});
|
||||
@@ -196,11 +214,11 @@ function toLastView(visitedViews: TagView[], view?: any) {
|
||||
} 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") {
|
||||
if (view.name === 'Dashboard') {
|
||||
// to reload home page
|
||||
router.replace({ path: "/redirect" + view.fullPath });
|
||||
router.replace({ path: '/redirect' + view.fullPath });
|
||||
} else {
|
||||
router.push("/");
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +297,7 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
<style lang="scss" scoped>
|
||||
.tags-view__container {
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
@@ -329,8 +347,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.tags-view__menu {
|
||||
|
||||
@@ -1,4 +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'
|
||||
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';
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
</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 { 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";
|
||||
import useStore from '@/store';
|
||||
|
||||
const { width } = useWindowSize();
|
||||
const WIDTH = 992;
|
||||
@@ -43,15 +43,15 @@ const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === "mobile",
|
||||
mobile: device.value === 'mobile'
|
||||
}));
|
||||
|
||||
watchEffect(() => {
|
||||
if (width.value < WIDTH) {
|
||||
app.toggleDevice("mobile");
|
||||
app.toggleDevice('mobile');
|
||||
app.closeSideBar(true);
|
||||
} else {
|
||||
app.toggleDevice("desktop");
|
||||
app.toggleDevice('desktop');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -61,8 +61,8 @@ function handleClickOutside() {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/mixin.scss";
|
||||
@import "@/styles/variables.module.scss";
|
||||
@import '@/styles/mixin.scss';
|
||||
@import '@/styles/variables.module.scss';
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
33
src/main.ts
33
src/main.ts
@@ -1,42 +1,43 @@
|
||||
import {createApp, Directive} from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from "@/router";
|
||||
import { createApp, Directive } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from '@/router';
|
||||
|
||||
import { createPinia } from "pinia"
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/theme-chalk/index.css'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import '@/permission'
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/theme-chalk/index.css';
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
import '@/permission';
|
||||
|
||||
// 引入svg注册脚本
|
||||
import 'virtual:svg-icons-register';
|
||||
|
||||
// 国际化
|
||||
import i18n from "@/lang/index";
|
||||
import i18n from '@/lang/index';
|
||||
|
||||
// 自定义样式
|
||||
import '@/styles/index.scss'
|
||||
import '@/styles/index.scss';
|
||||
|
||||
// 根据字典编码获取字典列表全局方法
|
||||
import {listDictsByCode} from '@/api/system/dict'
|
||||
import { listDictsByCode } from '@/api/system/dict';
|
||||
|
||||
const app = createApp(App)
|
||||
const app = createApp(App);
|
||||
|
||||
// 自定义指令
|
||||
import * as directive from "@/directive";
|
||||
import * as directive from '@/directive';
|
||||
|
||||
Object.keys(directive).forEach(key => {
|
||||
app.directive(key, (directive as { [key: string]: Directive })[key]);
|
||||
});
|
||||
|
||||
// 全局方法
|
||||
app.config.globalProperties.$listDictsByCode = listDictsByCode
|
||||
app.config.globalProperties.$listDictsByCode = listDictsByCode;
|
||||
|
||||
// 注册全局组件
|
||||
app.component('Pagination', Pagination)
|
||||
app
|
||||
.component('Pagination', Pagination)
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.use(ElementPlus)
|
||||
.use(i18n)
|
||||
.mount('#app')
|
||||
.mount('#app');
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
import router from "@/router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import useStore from "@/store";
|
||||
import router from '@/router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import useStore from '@/store';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css'
|
||||
NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏
|
||||
import 'nprogress/nprogress.css';
|
||||
NProgress.configure({ showSpinner: false }); // 进度环显示/隐藏
|
||||
|
||||
// 白名单路由
|
||||
const whiteList = ['/login', '/auth-redirect']
|
||||
const whiteList = ['/login', '/auth-redirect'];
|
||||
|
||||
router.beforeEach(async (to, form, next) => {
|
||||
NProgress.start()
|
||||
const { user, permission } = useStore()
|
||||
const hasToken = user.token
|
||||
NProgress.start();
|
||||
const { user, permission } = useStore();
|
||||
const hasToken = user.token;
|
||||
if (hasToken) {
|
||||
// 登录成功,跳转到首页
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
next({ path: '/' });
|
||||
NProgress.done();
|
||||
} else {
|
||||
const hasGetUserInfo = user.roles.length > 0
|
||||
const hasGetUserInfo = user.roles.length > 0;
|
||||
if (hasGetUserInfo) {
|
||||
next()
|
||||
next();
|
||||
} else {
|
||||
try {
|
||||
await user.getUserInfo()
|
||||
const roles = user.roles
|
||||
const accessRoutes: any = await permission.generateRoutes(roles)
|
||||
await user.getUserInfo();
|
||||
const roles = user.roles;
|
||||
const accessRoutes: any = await permission.generateRoutes(roles);
|
||||
accessRoutes.forEach((route: any) => {
|
||||
router.addRoute(route)
|
||||
})
|
||||
next({ ...to, replace: true })
|
||||
router.addRoute(route);
|
||||
});
|
||||
next({ ...to, replace: true });
|
||||
} catch (error) {
|
||||
// 移除 token 并跳转登录页
|
||||
await user.resetToken()
|
||||
ElMessage.error(error as any || 'Has Error')
|
||||
next(`/login?redirect=${to.path}`)
|
||||
NProgress.done()
|
||||
await user.resetToken();
|
||||
ElMessage.error((error as any) || 'Has Error');
|
||||
next(`/login?redirect=${to.path}`);
|
||||
NProgress.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 未登录可以访问白名单页面(登录页面)
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
next();
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`)
|
||||
NProgress.done()
|
||||
next(`/login?redirect=${to.path}`);
|
||||
NProgress.done();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
import useStore from "@/store";
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import useStore from '@/store';
|
||||
|
||||
export const Layout = () => import('@/layout/index.vue')
|
||||
export const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
// 参数说明: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
|
||||
// 静态路由
|
||||
@@ -97,7 +97,7 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
]
|
||||
}*/
|
||||
]
|
||||
];
|
||||
|
||||
// 创建路由
|
||||
const router = createRouter({
|
||||
@@ -105,17 +105,17 @@ const router = createRouter({
|
||||
routes: constantRoutes as RouteRecordRaw[],
|
||||
// 刷新时,滚动条位置还原
|
||||
scrollBehavior: () => ({ left: 0, top: 0 })
|
||||
})
|
||||
});
|
||||
|
||||
// 重置路由
|
||||
export function resetRouter() {
|
||||
const { permission } = useStore()
|
||||
permission.routes.forEach((route) => {
|
||||
const name = route.name
|
||||
const { permission } = useStore();
|
||||
permission.routes.forEach(route => {
|
||||
const name = route.name;
|
||||
if (name && router.hasRoute(name)) {
|
||||
router.removeRoute(name)
|
||||
router.removeRoute(name);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
interface DefaultSettings {
|
||||
title: string,
|
||||
showSettings: boolean,
|
||||
tagsView: boolean,
|
||||
fixedHeader: boolean,
|
||||
sidebarLogo: boolean,
|
||||
errorLog: string
|
||||
title: string;
|
||||
showSettings: boolean;
|
||||
tagsView: boolean;
|
||||
fixedHeader: boolean;
|
||||
sidebarLogo: boolean;
|
||||
errorLog: string;
|
||||
}
|
||||
|
||||
|
||||
const defaultSettings: DefaultSettings = {
|
||||
title: 'vue3-element-admin',
|
||||
showSettings: true,
|
||||
@@ -16,6 +15,6 @@ const defaultSettings: DefaultSettings = {
|
||||
// 是否显示Logo
|
||||
sidebarLogo: true,
|
||||
errorLog: 'production'
|
||||
}
|
||||
};
|
||||
|
||||
export default defaultSettings
|
||||
export default defaultSettings;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import useUserStore from './modules/user'
|
||||
import useAppStore from './modules/app'
|
||||
import usePermissionStore from './modules/permission'
|
||||
import useSettingStore from './modules/settings'
|
||||
import useTagsViewStore from './modules/tagsView'
|
||||
import useUserStore from './modules/user';
|
||||
import useAppStore from './modules/app';
|
||||
import usePermissionStore from './modules/permission';
|
||||
import useSettingStore from './modules/settings';
|
||||
import useTagsViewStore from './modules/tagsView';
|
||||
|
||||
const useStore = () => ({
|
||||
user: useUserStore(),
|
||||
@@ -10,6 +10,6 @@ const useStore = () => ({
|
||||
permission: usePermissionStore(),
|
||||
setting: useSettingStore(),
|
||||
tagsView: useTagsViewStore()
|
||||
})
|
||||
});
|
||||
|
||||
export default useStore
|
||||
export default useStore;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { AppState } from "@/types";
|
||||
import { localStorage } from "@/utils/storage";
|
||||
import { defineStore } from "pinia";
|
||||
import { getLanguage } from '@/lang/index'
|
||||
import { AppState } from '@/types';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
import { defineStore } from 'pinia';
|
||||
import { getLanguage } from '@/lang/index';
|
||||
|
||||
const useAppStore = defineStore({
|
||||
id: "app",
|
||||
id: 'app',
|
||||
state: (): AppState => ({
|
||||
device: 'desktop',
|
||||
sidebar: {
|
||||
opened: localStorage.get('sidebarStatus') ? !!+localStorage.get('sidebarStatus') : true,
|
||||
opened: localStorage.get('sidebarStatus')
|
||||
? !!+localStorage.get('sidebarStatus')
|
||||
: true,
|
||||
withoutAnimation: false
|
||||
},
|
||||
language: getLanguage(),
|
||||
@@ -16,31 +18,31 @@ const useAppStore = defineStore({
|
||||
}),
|
||||
actions: {
|
||||
toggleSidebar() {
|
||||
this.sidebar.opened = !this.sidebar.opened
|
||||
this.sidebar.withoutAnimation = false
|
||||
this.sidebar.opened = !this.sidebar.opened;
|
||||
this.sidebar.withoutAnimation = false;
|
||||
if (this.sidebar.opened) {
|
||||
localStorage.set('sidebarStatus', 1)
|
||||
localStorage.set('sidebarStatus', 1);
|
||||
} else {
|
||||
localStorage.set('sidebarStatus', 0)
|
||||
localStorage.set('sidebarStatus', 0);
|
||||
}
|
||||
},
|
||||
closeSideBar(withoutAnimation: any) {
|
||||
localStorage.set('sidebarStatus', 0)
|
||||
this.sidebar.opened = false
|
||||
this.sidebar.withoutAnimation = withoutAnimation
|
||||
localStorage.set('sidebarStatus', 0);
|
||||
this.sidebar.opened = false;
|
||||
this.sidebar.withoutAnimation = withoutAnimation;
|
||||
},
|
||||
toggleDevice(device: string) {
|
||||
this.device = device
|
||||
this.device = device;
|
||||
},
|
||||
setSize(size: string) {
|
||||
this.size = size
|
||||
localStorage.set('size', size)
|
||||
this.size = size;
|
||||
localStorage.set('size', size);
|
||||
},
|
||||
setLanguage(language: string) {
|
||||
this.language = language
|
||||
localStorage.set('language', language)
|
||||
this.language = language;
|
||||
localStorage.set('language', language);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export default useAppStore;
|
||||
@@ -1,33 +1,36 @@
|
||||
import { PermissionState } from "@/types";
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
import { defineStore } from "pinia";
|
||||
import { constantRoutes } from '@/router'
|
||||
import { listRoutes } from "@/api/system/menu";
|
||||
import { PermissionState } from '@/types';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
import { constantRoutes } from '@/router';
|
||||
import { listRoutes } from '@/api/system/menu';
|
||||
|
||||
const modules = import.meta.glob("../../views/**/**.vue");
|
||||
export const Layout = () => import('@/layout/index.vue')
|
||||
const modules = import.meta.glob('../../views/**/**.vue');
|
||||
export const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
||||
if (route.meta && route.meta.roles) {
|
||||
if (roles.includes('ROOT')) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
return roles.some(role => {
|
||||
if (route.meta?.roles !== undefined) {
|
||||
return (route.meta.roles as string[]).includes(role);
|
||||
}
|
||||
})
|
||||
}
|
||||
return false
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
||||
const res: RouteRecordRaw[] = []
|
||||
export const filterAsyncRoutes = (
|
||||
routes: RouteRecordRaw[],
|
||||
roles: string[]
|
||||
) => {
|
||||
const res: RouteRecordRaw[] = [];
|
||||
routes.forEach(route => {
|
||||
const tmp = { ...route } as any
|
||||
const tmp = { ...route } as any;
|
||||
if (hasPermission(roles, tmp)) {
|
||||
if (tmp.component == 'Layout') {
|
||||
tmp.component = Layout
|
||||
tmp.component = Layout;
|
||||
} else {
|
||||
const component = modules[`../../views/${tmp.component}.vue`] as any;
|
||||
if (component) {
|
||||
@@ -36,41 +39,42 @@ export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) =>
|
||||
tmp.component = modules[`../../views/error-page/404.vue`];
|
||||
}
|
||||
}
|
||||
res.push(tmp)
|
||||
res.push(tmp);
|
||||
|
||||
if (tmp.children) {
|
||||
tmp.children = filterAsyncRoutes(tmp.children, roles)
|
||||
tmp.children = filterAsyncRoutes(tmp.children, roles);
|
||||
}
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
const usePermissionStore = defineStore({
|
||||
id: "permission",
|
||||
id: 'permission',
|
||||
state: (): PermissionState => ({
|
||||
routes: [],
|
||||
addRoutes: []
|
||||
}),
|
||||
actions: {
|
||||
setRoutes(routes: RouteRecordRaw[]) {
|
||||
this.addRoutes = routes
|
||||
this.routes = constantRoutes.concat(routes)
|
||||
this.addRoutes = routes;
|
||||
this.routes = constantRoutes.concat(routes);
|
||||
},
|
||||
generateRoutes(roles: string[]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
listRoutes().then(response => {
|
||||
const asyncRoutes = response.data
|
||||
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
|
||||
this.setRoutes(accessedRoutes)
|
||||
resolve(accessedRoutes)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
listRoutes()
|
||||
.then(response => {
|
||||
const asyncRoutes = response.data;
|
||||
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
||||
this.setRoutes(accessedRoutes);
|
||||
resolve(accessedRoutes);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export default usePermissionStore;
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { SettingState } from "@/types";
|
||||
import defaultSettings from '../../settings'
|
||||
import { localStorage } from "@/utils/storage";
|
||||
import { defineStore } from 'pinia';
|
||||
import { SettingState } from '@/types';
|
||||
import defaultSettings from '../../settings';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
|
||||
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
|
||||
const el = document.documentElement
|
||||
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings;
|
||||
const el = document.documentElement;
|
||||
|
||||
export const useSettingStore = defineStore({
|
||||
id: "setting",
|
||||
id: 'setting',
|
||||
state: (): SettingState => ({
|
||||
theme: localStorage.get("theme") || getComputedStyle(el).getPropertyValue(`--el-color-primary`),
|
||||
theme:
|
||||
localStorage.get('theme') ||
|
||||
getComputedStyle(el).getPropertyValue(`--el-color-primary`),
|
||||
showSettings: showSettings,
|
||||
tagsView: localStorage.get("tagsView") != null ? localStorage.get("tagsView") : tagsView,
|
||||
tagsView:
|
||||
localStorage.get('tagsView') != null
|
||||
? localStorage.get('tagsView')
|
||||
: tagsView,
|
||||
fixedHeader: fixedHeader,
|
||||
sidebarLogo: sidebarLogo,
|
||||
sidebarLogo: sidebarLogo
|
||||
}),
|
||||
actions: {
|
||||
async changeSetting(payload: { key: string, value: any }) {
|
||||
const { key, value } = payload
|
||||
async changeSetting(payload: { key: string; value: any }) {
|
||||
const { key, value } = payload;
|
||||
switch (key) {
|
||||
case 'theme':
|
||||
this.theme = value
|
||||
break
|
||||
this.theme = value;
|
||||
break;
|
||||
case 'showSettings':
|
||||
this.showSettings = value
|
||||
break
|
||||
this.showSettings = value;
|
||||
break;
|
||||
case 'fixedHeader':
|
||||
this.fixedHeader = value
|
||||
break
|
||||
this.fixedHeader = value;
|
||||
break;
|
||||
case 'tagsView':
|
||||
this.tagsView = value
|
||||
localStorage.set("tagsView", value)
|
||||
break
|
||||
this.tagsView = value;
|
||||
localStorage.set('tagsView', value);
|
||||
break;
|
||||
case 'sidebarLogo':
|
||||
this.sidebarLogo = value
|
||||
break
|
||||
this.sidebarLogo = value;
|
||||
break;
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export default useSettingStore;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { TagsViewState } from "@/types";
|
||||
import { defineStore } from 'pinia';
|
||||
import { TagsViewState } from '@/types';
|
||||
|
||||
const useTagsViewStore = defineStore({
|
||||
id: "tagsView",
|
||||
id: 'tagsView',
|
||||
state: (): TagsViewState => ({
|
||||
visitedViews: [],
|
||||
cachedViews: []
|
||||
}),
|
||||
actions: {
|
||||
addVisitedView(view: any) {
|
||||
if (this.visitedViews.some(v => v.path === view.path)) return
|
||||
if (this.visitedViews.some(v => v.path === view.path)) return;
|
||||
this.visitedViews.push(
|
||||
Object.assign({}, view, {
|
||||
title: view.meta?.title || 'no-name'
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
addCachedView(view: any) {
|
||||
if (this.cachedViews.includes(view.name)) return
|
||||
if (this.cachedViews.includes(view.name)) return;
|
||||
if (!view.meta.noCache) {
|
||||
this.cachedViews.push(view.name)
|
||||
this.cachedViews.push(view.name);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -27,149 +27,149 @@ const useTagsViewStore = defineStore({
|
||||
return new Promise(resolve => {
|
||||
for (const [i, v] of this.visitedViews.entries()) {
|
||||
if (v.path === view.path) {
|
||||
this.visitedViews.splice(i, 1)
|
||||
break
|
||||
this.visitedViews.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
|
||||
resolve([...this.visitedViews]);
|
||||
});
|
||||
},
|
||||
delCachedView(view: any) {
|
||||
return new Promise(resolve => {
|
||||
const index = this.cachedViews.indexOf(view.name)
|
||||
index > -1 && this.cachedViews.splice(index, 1)
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
|
||||
const index = this.cachedViews.indexOf(view.name);
|
||||
index > -1 && this.cachedViews.splice(index, 1);
|
||||
resolve([...this.cachedViews]);
|
||||
});
|
||||
},
|
||||
|
||||
delOtherVisitedViews(view: any) {
|
||||
return new Promise(resolve => {
|
||||
this.visitedViews = this.visitedViews.filter(v => {
|
||||
return v.meta?.affix || v.path === view.path
|
||||
})
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
|
||||
return v.meta?.affix || v.path === view.path;
|
||||
});
|
||||
resolve([...this.visitedViews]);
|
||||
});
|
||||
},
|
||||
delOtherCachedViews(view: any) {
|
||||
return new Promise(resolve => {
|
||||
const index = this.cachedViews.indexOf(view.name)
|
||||
const index = this.cachedViews.indexOf(view.name);
|
||||
if (index > -1) {
|
||||
this.cachedViews = this.cachedViews.slice(index, index + 1)
|
||||
this.cachedViews = this.cachedViews.slice(index, index + 1);
|
||||
} else {
|
||||
// if index = -1, there is no cached tags
|
||||
this.cachedViews = []
|
||||
this.cachedViews = [];
|
||||
}
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
|
||||
resolve([...this.cachedViews]);
|
||||
});
|
||||
},
|
||||
|
||||
updateVisitedView(view: any) {
|
||||
for (let v of this.visitedViews) {
|
||||
if (v.path === view.path) {
|
||||
v = Object.assign(v, view)
|
||||
break
|
||||
v = Object.assign(v, view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
addView(view: any) {
|
||||
this.addVisitedView(view)
|
||||
this.addCachedView(view)
|
||||
this.addVisitedView(view);
|
||||
this.addCachedView(view);
|
||||
},
|
||||
delView(view: any) {
|
||||
return new Promise(resolve => {
|
||||
this.delVisitedView(view)
|
||||
this.delCachedView(view)
|
||||
this.delVisitedView(view);
|
||||
this.delCachedView(view);
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
delOtherViews(view: any) {
|
||||
return new Promise(resolve => {
|
||||
this.delOtherVisitedViews(view)
|
||||
this.delOtherCachedViews(view)
|
||||
this.delOtherVisitedViews(view);
|
||||
this.delOtherCachedViews(view);
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
delLeftViews(view: any) {
|
||||
return new Promise(resolve => {
|
||||
const currIndex = this.visitedViews.findIndex(v => v.path === view.path)
|
||||
const currIndex = this.visitedViews.findIndex(
|
||||
v => v.path === view.path
|
||||
);
|
||||
if (currIndex === -1) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.visitedViews = this.visitedViews.filter((item, index) => {
|
||||
// affix:true 固定tag,例如“首页”
|
||||
if (index >= currIndex || (item.meta && item.meta.affix)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
const cacheIndex = this.cachedViews.indexOf(item.name as string)
|
||||
const cacheIndex = this.cachedViews.indexOf(item.name as string);
|
||||
if (cacheIndex > -1) {
|
||||
this.cachedViews.splice(cacheIndex, 1)
|
||||
this.cachedViews.splice(cacheIndex, 1);
|
||||
}
|
||||
return false
|
||||
})
|
||||
return false;
|
||||
});
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
delRightViews(view: any) {
|
||||
return new Promise(resolve => {
|
||||
const currIndex = this.visitedViews.findIndex(v => v.path === view.path)
|
||||
const currIndex = this.visitedViews.findIndex(
|
||||
v => v.path === view.path
|
||||
);
|
||||
if (currIndex === -1) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.visitedViews = this.visitedViews.filter((item, index) => {
|
||||
// affix:true 固定tag,例如“首页”
|
||||
if (index <= currIndex || (item.meta && item.meta.affix)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
const cacheIndex = this.cachedViews.indexOf(item.name as string)
|
||||
const cacheIndex = this.cachedViews.indexOf(item.name as string);
|
||||
if (cacheIndex > -1) {
|
||||
this.cachedViews.splice(cacheIndex, 1)
|
||||
this.cachedViews.splice(cacheIndex, 1);
|
||||
}
|
||||
return false
|
||||
})
|
||||
return false;
|
||||
});
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
delAllViews() {
|
||||
return new Promise(resolve => {
|
||||
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix)
|
||||
this.visitedViews = affixTags
|
||||
this.cachedViews = []
|
||||
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
|
||||
this.visitedViews = affixTags;
|
||||
this.cachedViews = [];
|
||||
resolve({
|
||||
visitedViews: [...this.visitedViews],
|
||||
cachedViews: [...this.cachedViews]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
delAllVisitedViews() {
|
||||
return new Promise(resolve => {
|
||||
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix)
|
||||
this.visitedViews = affixTags
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
const affixTags = this.visitedViews.filter(tag => tag.meta?.affix);
|
||||
this.visitedViews = affixTags;
|
||||
resolve([...this.visitedViews]);
|
||||
});
|
||||
},
|
||||
delAllCachedViews() {
|
||||
return new Promise(resolve => {
|
||||
this.cachedViews = []
|
||||
resolve([...this.cachedViews])
|
||||
})
|
||||
},
|
||||
this.cachedViews = [];
|
||||
resolve([...this.cachedViews]);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
export default useTagsViewStore;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { LoginFormData, UserState } from "@/types";
|
||||
import { localStorage } from "@/utils/storage";
|
||||
import { login, logout } from "@/api/login";
|
||||
import { getUserInfo } from "@/api/system/user";
|
||||
import { resetRouter } from "@/router";
|
||||
import { defineStore } from 'pinia';
|
||||
import { LoginFormData, UserState } from '@/types';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
import { login, logout } from '@/api/login';
|
||||
import { getUserInfo } from '@/api/system/user';
|
||||
import { resetRouter } from '@/router';
|
||||
|
||||
const useUserStore = defineStore({
|
||||
id: "user",
|
||||
id: 'user',
|
||||
state: (): UserState => ({
|
||||
token: localStorage.get('token') || '',
|
||||
nickname: '',
|
||||
@@ -16,7 +16,7 @@ const useUserStore = defineStore({
|
||||
}),
|
||||
actions: {
|
||||
async RESET_STATE() {
|
||||
this.$reset()
|
||||
this.$reset();
|
||||
},
|
||||
/**
|
||||
* 用户登录请求
|
||||
@@ -27,66 +27,69 @@ const useUserStore = defineStore({
|
||||
* uuid: 匹配正确验证码的 key
|
||||
*/
|
||||
login(userInfo: LoginFormData) {
|
||||
const { username, password, code, uuid } = userInfo
|
||||
const { username, password, code, uuid } = userInfo;
|
||||
return new Promise((resolve, reject) => {
|
||||
login(
|
||||
{
|
||||
login({
|
||||
username: username.trim(),
|
||||
password: password,
|
||||
grant_type: 'captcha',
|
||||
code: code,
|
||||
uuid: uuid
|
||||
}
|
||||
).then(response => {
|
||||
const { access_token, token_type } = response.data
|
||||
const accessToken = token_type + " " + access_token
|
||||
localStorage.set("token", accessToken)
|
||||
this.token = accessToken
|
||||
resolve(access_token)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
.then(response => {
|
||||
const { access_token, token_type } = response.data;
|
||||
const accessToken = token_type + ' ' + access_token;
|
||||
localStorage.set('token', accessToken);
|
||||
this.token = accessToken;
|
||||
resolve(access_token);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 获取用户信息(昵称、头像、角色集合、权限集合)
|
||||
*/
|
||||
getUserInfo() {
|
||||
return new Promise(((resolve, reject) => {
|
||||
getUserInfo().then(({data}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
.then(({ data }) => {
|
||||
if (!data) {
|
||||
return reject('Verification failed, please Login again.')
|
||||
return reject('Verification failed, please Login again.');
|
||||
}
|
||||
const { nickname, avatar, roles, perms } = data
|
||||
const { nickname, avatar, roles, perms } = data;
|
||||
if (!roles || roles.length <= 0) {
|
||||
reject('getUserInfo: roles must be a non-null array!')
|
||||
reject('getUserInfo: roles must be a non-null array!');
|
||||
}
|
||||
this.nickname = nickname
|
||||
this.avatar = avatar
|
||||
this.roles = roles
|
||||
this.perms = perms
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
this.nickname = nickname;
|
||||
this.avatar = avatar;
|
||||
this.roles = roles;
|
||||
this.perms = perms;
|
||||
resolve(data);
|
||||
})
|
||||
})
|
||||
)
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
logout() {
|
||||
return new Promise(((resolve, reject) => {
|
||||
logout().then(() => {
|
||||
localStorage.remove('token')
|
||||
this.RESET_STATE()
|
||||
resetRouter()
|
||||
resolve(null)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
return new Promise((resolve, reject) => {
|
||||
logout()
|
||||
.then(() => {
|
||||
localStorage.remove('token');
|
||||
this.RESET_STATE();
|
||||
resetRouter();
|
||||
resolve(null);
|
||||
})
|
||||
}))
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -94,12 +97,12 @@ const useUserStore = defineStore({
|
||||
*/
|
||||
resetToken() {
|
||||
return new Promise(resolve => {
|
||||
localStorage.remove('token')
|
||||
this.RESET_STATE()
|
||||
resolve(null)
|
||||
})
|
||||
localStorage.remove('token');
|
||||
this.RESET_STATE();
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export default useUserStore;
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
input[type='file'] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
// dropdown
|
||||
.el-dropdown-menu {
|
||||
a {
|
||||
display: block
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@
|
||||
}
|
||||
|
||||
// 表格表头和表体未对齐
|
||||
.el-table__header col[name="gutter"] {
|
||||
.el-table__header col[name='gutter'] {
|
||||
display: table-cell !important;
|
||||
}
|
||||
@@ -11,7 +11,8 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
|
||||
Microsoft YaHei, Arial, sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
@@ -55,7 +56,7 @@ div:focus {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
content: ' ';
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@mixin clearfix {
|
||||
&:after {
|
||||
content: "";
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#app {
|
||||
.main-container {
|
||||
min-height: 100%;
|
||||
transition: margin-left .28s;
|
||||
transition: margin-left 0.28s;
|
||||
margin-left: $sideBarWidth;
|
||||
position: relative;
|
||||
}
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
// reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
|
||||
0s padding-right ease-in-out;
|
||||
}
|
||||
|
||||
.scrollbar-wrapper {
|
||||
@@ -165,7 +166,7 @@
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
transition: transform .28s;
|
||||
transition: transform 0.28s;
|
||||
width: $sideBarWidth !important;
|
||||
}
|
||||
|
||||
@@ -179,7 +180,6 @@
|
||||
}
|
||||
|
||||
.withoutAnimation {
|
||||
|
||||
.main-container,
|
||||
.sidebar-container {
|
||||
transition: none;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/* fade-transform */
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all .5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
@@ -30,7 +30,7 @@
|
||||
/* breadcrumb transition */
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all .5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
|
||||
.breadcrumb-move {
|
||||
transition: all .5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// sidebar
|
||||
$menuText: #bfcbd9;
|
||||
$menuActiveText:#409EFF;
|
||||
$menuActiveText: #409eff;
|
||||
$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951
|
||||
|
||||
$menuBg: #304156;
|
||||
|
||||
10
src/types/api/base.d.ts
vendored
10
src/types/api/base.d.ts
vendored
@@ -1,11 +1,9 @@
|
||||
|
||||
export interface PageQueryParam {
|
||||
pageNum: number,
|
||||
pageSize: number
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface PageResult<T> {
|
||||
list: T,
|
||||
total: number
|
||||
list: T;
|
||||
total: number;
|
||||
}
|
||||
|
||||
|
||||
4
src/types/api/oms/order.d.ts
vendored
4
src/types/api/oms/order.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 订单查询参数类型声明
|
||||
@@ -38,7 +38,7 @@ export interface OrderItem {
|
||||
/**
|
||||
* 订单分页项类型声明
|
||||
*/
|
||||
export type OrderPageResult = PageResult<Order[]>
|
||||
export type OrderPageResult = PageResult<Order[]>;
|
||||
|
||||
/**
|
||||
* 订单表单类型声明
|
||||
|
||||
15
src/types/api/pms/brand.d.ts
vendored
15
src/types/api/pms/brand.d.ts
vendored
@@ -1,13 +1,12 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 品牌查询参数类型声明
|
||||
*/
|
||||
export interface BrandQueryParam extends PageQueryParam {
|
||||
name?: string
|
||||
name?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 品牌分页列表项声明
|
||||
*/
|
||||
@@ -21,14 +20,14 @@ export interface BrandItem {
|
||||
/**
|
||||
* 品牌分页项类型声明
|
||||
*/
|
||||
export type BrandPageResult = PageResult<BrandItem[]>
|
||||
export type BrandPageResult = PageResult<BrandItem[]>;
|
||||
|
||||
/**
|
||||
* 品牌表单类型声明
|
||||
*/
|
||||
export interface BrandFormData {
|
||||
id: number | undefined,
|
||||
name: string,
|
||||
logoUrl: string,
|
||||
sort: number
|
||||
id: number | undefined;
|
||||
name: string;
|
||||
logoUrl: string;
|
||||
sort: number;
|
||||
}
|
||||
36
src/types/api/pms/goods.d.ts
vendored
36
src/types/api/pms/goods.d.ts
vendored
@@ -1,11 +1,11 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 商品查询参数类型声明
|
||||
*/
|
||||
export interface GoodsQueryParam extends PageQueryParam {
|
||||
name?: stirng,
|
||||
categoryId?: number
|
||||
name?: stirng;
|
||||
categoryId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,25 +48,23 @@ export interface SkuItem {
|
||||
/**
|
||||
* 商品分页项类型声明
|
||||
*/
|
||||
export type GoodsPageResult = PageResult<GoodsItem[]>
|
||||
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[]
|
||||
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[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
6
src/types/api/sms/advert.d.ts
vendored
6
src/types/api/sms/advert.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 广告查询参数类型声明
|
||||
*/
|
||||
export interface AdvertQueryParam extends PageQueryParam {
|
||||
title?: string
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ export interface AdvertItem {
|
||||
/**
|
||||
* 广告分页项类型声明
|
||||
*/
|
||||
export type AdvertPageResult = PageResult<AdvertItem[]>
|
||||
export type AdvertPageResult = PageResult<AdvertItem[]>;
|
||||
|
||||
/**
|
||||
* 广告表单类型声明
|
||||
|
||||
7
src/types/api/system/client.d.ts
vendored
7
src/types/api/system/client.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 客户端查询参数类型声明
|
||||
@@ -7,10 +7,9 @@ export interface ClientQueryParam extends PageQueryParam {
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
clientId: string | undefined
|
||||
clientId: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 客户端分页列表项声明
|
||||
*/
|
||||
@@ -31,7 +30,7 @@ export interface ClientItem {
|
||||
/**
|
||||
* 客户端分页项类型声明
|
||||
*/
|
||||
export type ClientPageResult = PageResult<ClientItem[]>
|
||||
export type ClientPageResult = PageResult<ClientItem[]>;
|
||||
|
||||
/**
|
||||
* 客户端表单类型声明
|
||||
|
||||
15
src/types/api/system/dept.d.ts
vendored
15
src/types/api/system/dept.d.ts
vendored
@@ -1,10 +1,9 @@
|
||||
|
||||
/**
|
||||
* 部门查询参数类型声明
|
||||
*/
|
||||
export interface DeptQueryParam {
|
||||
name: string | undefined,
|
||||
status: number | undefined
|
||||
name: string | undefined;
|
||||
status: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,9 +27,9 @@ export interface DeptItem {
|
||||
* 部门表单类型声明
|
||||
*/
|
||||
export interface DeptFormData {
|
||||
id?: string,
|
||||
parentId: string,
|
||||
name: string,
|
||||
sort: number,
|
||||
status: number
|
||||
id?: string;
|
||||
parentId: string;
|
||||
name: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
}
|
||||
21
src/types/api/system/dict.d.ts
vendored
21
src/types/api/system/dict.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 字典查询参数类型声明
|
||||
@@ -7,10 +7,9 @@ export interface DictQueryParam extends PageQueryParam {
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
name: string | undefined
|
||||
name: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 字典分页列表项声明
|
||||
*/
|
||||
@@ -25,17 +24,17 @@ export interface Dict {
|
||||
/**
|
||||
* 字典分页项类型声明
|
||||
*/
|
||||
export type DictPageResult = PageResult<Dict[]>
|
||||
export type DictPageResult = PageResult<Dict[]>;
|
||||
|
||||
/**
|
||||
* 字典表单类型声明
|
||||
*/
|
||||
export interface DictFormData {
|
||||
id: number | undefined,
|
||||
name: string,
|
||||
code: string,
|
||||
status: number,
|
||||
remark: string
|
||||
id: number | undefined;
|
||||
name: string;
|
||||
code: string;
|
||||
status: number;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,14 +68,14 @@ export interface DictItem {
|
||||
/**
|
||||
* 字典分页项类型声明
|
||||
*/
|
||||
export type DictItemPageResult = PageResult<DictItem[]>
|
||||
export type DictItemPageResult = PageResult<DictItem[]>;
|
||||
|
||||
/**
|
||||
* 字典表单类型声明
|
||||
*/
|
||||
export interface DictItemFormData {
|
||||
id?: number;
|
||||
dictCode?:string,
|
||||
dictCode?: string;
|
||||
dictName?: string;
|
||||
name: string;
|
||||
code: string;
|
||||
|
||||
19
src/types/api/system/login.d.ts
vendored
19
src/types/api/system/login.d.ts
vendored
@@ -2,26 +2,25 @@
|
||||
* 登录表单类型声明
|
||||
*/
|
||||
export interface LoginFormData {
|
||||
username: string,
|
||||
password: string,
|
||||
grant_type: string,
|
||||
code: string,
|
||||
uuid: string,
|
||||
username: string;
|
||||
password: string;
|
||||
grant_type: string;
|
||||
code: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录响应类型声明
|
||||
*/
|
||||
export interface LoginResponseData {
|
||||
access_token: string,
|
||||
token_type: string
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码类型声明
|
||||
*/
|
||||
export interface Captcha {
|
||||
img: string,
|
||||
uuid: string
|
||||
img: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
|
||||
23
src/types/api/system/menu.d.ts
vendored
23
src/types/api/system/menu.d.ts
vendored
@@ -1,9 +1,8 @@
|
||||
|
||||
/**
|
||||
* 菜单查询参数类型声明
|
||||
*/
|
||||
export interface MenuQueryParam {
|
||||
name?: string
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,39 +29,39 @@ export interface MenuFormData {
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
id?: string,
|
||||
id?: string;
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
parentId: string,
|
||||
parentId: string;
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
name: string,
|
||||
name: string;
|
||||
/**
|
||||
* 菜单是否可见(1:是;0:否;)
|
||||
*/
|
||||
visible: number,
|
||||
icon?: string,
|
||||
visible: number;
|
||||
icon?: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort: number,
|
||||
sort: number;
|
||||
/**
|
||||
* 组件路径
|
||||
*/
|
||||
component?: string,
|
||||
component?: string;
|
||||
/**
|
||||
* 路由路径
|
||||
*/
|
||||
path: string,
|
||||
path: string;
|
||||
/**
|
||||
* 跳转路由路径
|
||||
*/
|
||||
redirect?: string,
|
||||
redirect?: string;
|
||||
|
||||
/**
|
||||
* 菜单类型(1:菜单;2:目录;3:外链)
|
||||
*/
|
||||
type: string
|
||||
type: string;
|
||||
}
|
||||
14
src/types/api/system/perm.d.ts
vendored
14
src/types/api/system/perm.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 权限查询参数类型声明
|
||||
@@ -23,15 +23,15 @@ export interface PermItem {
|
||||
/**
|
||||
* 权限分页项类型声明
|
||||
*/
|
||||
export type PermPageResult = PageResult<PermItem[]>
|
||||
export type PermPageResult = PageResult<PermItem[]>;
|
||||
|
||||
/**
|
||||
* 权限表单类型声明
|
||||
*/
|
||||
export interface PermFormData {
|
||||
id: number|undefined,
|
||||
name: string,
|
||||
urlPerm: string,
|
||||
btnPerm: string,
|
||||
menuId: string
|
||||
id: number | undefined;
|
||||
name: string;
|
||||
urlPerm: string;
|
||||
btnPerm: string;
|
||||
menuId: string;
|
||||
}
|
||||
17
src/types/api/system/role.d.ts
vendored
17
src/types/api/system/role.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 角色查询参数类型声明
|
||||
*/
|
||||
export interface RoleQueryParam extends PageQueryParam {
|
||||
name?: string
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,19 +21,18 @@ export interface RoleItem {
|
||||
permissionIds?: any;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 角色分页项类型声明
|
||||
*/
|
||||
export type RolePageResult = PageResult<RoleItem[]>
|
||||
export type RolePageResult = PageResult<RoleItem[]>;
|
||||
|
||||
/**
|
||||
* 角色表单类型声明
|
||||
*/
|
||||
export interface RoleFormData {
|
||||
id: number|undefined,
|
||||
name: string,
|
||||
code: string,
|
||||
sort: number,
|
||||
status: number
|
||||
id: number | undefined;
|
||||
name: string;
|
||||
code: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
}
|
||||
44
src/types/api/system/user.d.ts
vendored
44
src/types/api/system/user.d.ts
vendored
@@ -1,22 +1,22 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 登录用户类型声明
|
||||
*/
|
||||
export interface UserInfo {
|
||||
nickname: string,
|
||||
avatar: string,
|
||||
roles: string[],
|
||||
perms: string[]
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
roles: string[];
|
||||
perms: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户查询参数类型声明
|
||||
*/
|
||||
export interface UserQueryParam extends PageQueryParam {
|
||||
keywords: string,
|
||||
status: number,
|
||||
deptId: number
|
||||
keywords: string;
|
||||
status: number;
|
||||
deptId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,29 +39,29 @@ export interface UserItem {
|
||||
/**
|
||||
* 用户分页项类型声明
|
||||
*/
|
||||
export type UserPageResult = PageResult<UserItem[]>
|
||||
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[]
|
||||
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[]
|
||||
deptId: number;
|
||||
roleIds: number[];
|
||||
}
|
||||
6
src/types/api/ums/member.d.ts
vendored
6
src/types/api/ums/member.d.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import { PageQueryParam, PageResult } from "../base"
|
||||
import { PageQueryParam, PageResult } from '../base';
|
||||
|
||||
/**
|
||||
* 会员查询参数类型声明
|
||||
*/
|
||||
export interface MemberQueryParam extends PageQueryParam {
|
||||
nickName?: string
|
||||
nickName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ export interface AddressItem {
|
||||
/**
|
||||
* 会员分页项类型声明
|
||||
*/
|
||||
export type MemberPageResult = PageResult<MemberItem[]>
|
||||
export type MemberPageResult = PageResult<MemberItem[]>;
|
||||
|
||||
/**
|
||||
* 会员表单类型声明
|
||||
|
||||
11
src/types/common.d.ts
vendored
11
src/types/common.d.ts
vendored
@@ -6,16 +6,15 @@
|
||||
* 弹窗属性类型声明
|
||||
*/
|
||||
export interface Dialog {
|
||||
title: string,
|
||||
visible: boolean
|
||||
title: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用组件选择项类型声明
|
||||
*/
|
||||
export interface Option {
|
||||
value: string,
|
||||
label: string
|
||||
children?: Option[]
|
||||
value: string;
|
||||
label: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
|
||||
35
src/types/index.d.ts
vendored
35
src/types/index.d.ts
vendored
@@ -1,22 +1,19 @@
|
||||
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/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 './api/lab/seata';
|
||||
|
||||
export * from './store';
|
||||
|
||||
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 './api/lab/seata'
|
||||
|
||||
export * from './store'
|
||||
|
||||
export * from './common'
|
||||
|
||||
export * from './common';
|
||||
|
||||
46
src/types/store.d.ts
vendored
46
src/types/store.d.ts
vendored
@@ -1,57 +1,55 @@
|
||||
|
||||
import { RouteRecordRaw, RouteLocationNormalized } from "vue-router";
|
||||
import { RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
|
||||
/**
|
||||
* 用户状态类型声明
|
||||
*/
|
||||
export interface AppState {
|
||||
device: string,
|
||||
device: string;
|
||||
sidebar: {
|
||||
opened: boolean,
|
||||
withoutAnimation: boolean
|
||||
},
|
||||
language: string,
|
||||
size: string
|
||||
opened: boolean;
|
||||
withoutAnimation: boolean;
|
||||
};
|
||||
language: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 权限类型声明
|
||||
*/
|
||||
export interface PermissionState {
|
||||
routes: RouteRecordRaw[]
|
||||
addRoutes: RouteRecordRaw[]
|
||||
routes: RouteRecordRaw[];
|
||||
addRoutes: RouteRecordRaw[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态类型声明
|
||||
*/
|
||||
export interface SettingState {
|
||||
theme: string,
|
||||
tagsView: boolean,
|
||||
fixedHeader: boolean,
|
||||
showSettings: boolean,
|
||||
sidebarLogo: boolean
|
||||
theme: string;
|
||||
tagsView: boolean;
|
||||
fixedHeader: boolean;
|
||||
showSettings: boolean;
|
||||
sidebarLogo: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签状态类型声明
|
||||
*/
|
||||
export interface TagView extends Partial<RouteLocationNormalized> {
|
||||
title?: string
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface TagsViewState {
|
||||
visitedViews: TagView[],
|
||||
cachedViews: string[]
|
||||
visitedViews: TagView[];
|
||||
cachedViews: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户状态类型声明
|
||||
*/
|
||||
export interface UserState {
|
||||
token: string,
|
||||
nickname: string,
|
||||
avatar: string,
|
||||
roles: string[],
|
||||
perms: string[]
|
||||
token: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
roles: string[];
|
||||
perms: string[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Show plural label if time is plural number
|
||||
* @param {number} time
|
||||
@@ -7,22 +6,22 @@
|
||||
*/
|
||||
function pluralize(time: number, label: string) {
|
||||
if (time === 1) {
|
||||
return time + label
|
||||
return time + label;
|
||||
}
|
||||
return time + label + 's'
|
||||
return time + label + 's';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} time
|
||||
*/
|
||||
export function timeAgo(time: number) {
|
||||
const between = Date.now() / 1000 - Number(time)
|
||||
const between = Date.now() / 1000 - Number(time);
|
||||
if (between < 3600) {
|
||||
return pluralize(~~(between / 60), ' minute')
|
||||
return pluralize(~~(between / 60), ' minute');
|
||||
} else if (between < 86400) {
|
||||
return pluralize(~~(between / 3600), ' hour')
|
||||
return pluralize(~~(between / 3600), ' hour');
|
||||
} else {
|
||||
return pluralize(~~(between / 86400), ' day')
|
||||
return pluralize(~~(between / 86400), ' day');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,19 +33,23 @@ export function timeAgo(time:number) {
|
||||
*/
|
||||
export function numberFormatter(num: number, digits: number) {
|
||||
const si = [
|
||||
{value: 1E18, symbol: 'E'},
|
||||
{value: 1E15, symbol: 'P'},
|
||||
{value: 1E12, symbol: 'T'},
|
||||
{value: 1E9, symbol: 'G'},
|
||||
{value: 1E6, symbol: 'M'},
|
||||
{value: 1E3, symbol: 'k'}
|
||||
]
|
||||
{ value: 1e18, symbol: 'E' },
|
||||
{ value: 1e15, symbol: 'P' },
|
||||
{ value: 1e12, symbol: 'T' },
|
||||
{ value: 1e9, symbol: 'G' },
|
||||
{ value: 1e6, symbol: 'M' },
|
||||
{ value: 1e3, symbol: 'k' }
|
||||
];
|
||||
for (let i = 0; i < si.length; i++) {
|
||||
if (num >= si[i].value) {
|
||||
return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
|
||||
return (
|
||||
(num / si[i].value)
|
||||
.toFixed(digits)
|
||||
.replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
|
||||
);
|
||||
}
|
||||
}
|
||||
return num.toString()
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +57,9 @@ export function numberFormatter(num:number, digits:number) {
|
||||
* @param {number} num
|
||||
*/
|
||||
export function toThousandFilter(num: number) {
|
||||
return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
|
||||
return (+num || 0)
|
||||
.toString()
|
||||
.replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,15 +67,14 @@ export function toThousandFilter(num:number) {
|
||||
* @param {String} string
|
||||
*/
|
||||
export function uppercaseFirst(string: string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 金额转换(分->元)
|
||||
* 100 => 1
|
||||
* @param {number} num
|
||||
*/
|
||||
export function moneyFormatter(num: number) {
|
||||
return '¥'+(isNaN(num) ? 0.00 : parseFloat((num / 100).toFixed(2)))
|
||||
return '¥' + (isNaN(num) ? 0.0 : parseFloat((num / 100).toFixed(2)));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// translate router.meta.title, be used in breadcrumb sidebar tagsview
|
||||
import i18n from "@/lang/index";
|
||||
import i18n from '@/lang/index';
|
||||
|
||||
export function generateTitle(title: any) {
|
||||
// 判断是否存在国际化配置,如果没有原生返回
|
||||
const hasKey = i18n.global.te('route.' + title)
|
||||
const hasKey = i18n.global.te('route.' + title);
|
||||
if (hasKey) {
|
||||
const translatedTitle = i18n.global.t('route.' + title)
|
||||
return translatedTitle
|
||||
const translatedTitle = i18n.global.t('route.' + title);
|
||||
return translatedTitle;
|
||||
}
|
||||
return title
|
||||
return title;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasClass(ele: HTMLElement, cls: string) {
|
||||
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
|
||||
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ export function hasClass(ele: HTMLElement, cls: string) {
|
||||
* @param {string} cls
|
||||
*/
|
||||
export function addClass(ele: HTMLElement, cls: string) {
|
||||
if (!hasClass(ele, cls)) ele.className += ' ' + cls
|
||||
if (!hasClass(ele, cls)) ele.className += ' ' + cls;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,8 +24,8 @@ export function addClass(ele: HTMLElement, cls: string) {
|
||||
*/
|
||||
export function removeClass(ele: HTMLElement, cls: string) {
|
||||
if (hasClass(ele, cls)) {
|
||||
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
|
||||
ele.className = ele.className.replace(reg, ' ')
|
||||
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
|
||||
ele.className = ele.className.replace(reg, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ export function mix(color1: string, color2: string, weight: number) {
|
||||
const r = Math.round(r1 * (1 - weight) + r2 * weight);
|
||||
const g = Math.round(g1 * (1 - weight) + g2 * weight);
|
||||
const b = Math.round(b1 * (1 - weight) + b2 * weight);
|
||||
const rStr = ("0" + (r || 0).toString(16)).slice(-2);
|
||||
const gStr = ("0" + (g || 0).toString(16)).slice(-2);
|
||||
const bStr = ("0" + (b || 0).toString(16)).slice(-2);
|
||||
return "#" + rStr + gStr + bStr;
|
||||
const rStr = ('0' + (r || 0).toString(16)).slice(-2);
|
||||
const gStr = ('0' + (g || 0).toString(16)).slice(-2);
|
||||
const bStr = ('0' + (b || 0).toString(16)).slice(-2);
|
||||
return '#' + rStr + gStr + bStr;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { localStorage } from "@/utils/storage";
|
||||
import useStore from "@/store";
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { localStorage } from '@/utils/storage';
|
||||
import useStore from '@/store';
|
||||
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 50000,
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' }
|
||||
})
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
if (!config.headers) {
|
||||
throw new Error(`Expected 'config' and 'config.headers' not to be undefined`);
|
||||
throw new Error(
|
||||
`Expected 'config' and 'config.headers' not to be undefined`
|
||||
);
|
||||
}
|
||||
const { user } = useStore()
|
||||
const { user } = useStore();
|
||||
if (user.token) {
|
||||
config.headers.Authorization = `${localStorage.get('token')}`;
|
||||
}
|
||||
return config
|
||||
}, (error) => {
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
@@ -33,34 +36,34 @@ service.interceptors.response.use(
|
||||
if (code === '00000') {
|
||||
return response.data;
|
||||
} else {
|
||||
|
||||
// 响应数据为二进制流处理(Excel导出)
|
||||
if (response.data instanceof ArrayBuffer) {
|
||||
return response
|
||||
return response;
|
||||
}
|
||||
|
||||
ElMessage({
|
||||
message: msg || '系统出错',
|
||||
type: 'error'
|
||||
})
|
||||
return Promise.reject(new Error(msg || 'Error'))
|
||||
});
|
||||
return Promise.reject(new Error(msg || 'Error'));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
const { code, msg } = error.response.data
|
||||
if (code === 'A0230') { // token 过期
|
||||
error => {
|
||||
const { code, msg } = error.response.data;
|
||||
if (code === 'A0230') {
|
||||
// token 过期
|
||||
localStorage.clear(); // 清除浏览器全部缓存
|
||||
window.location.href = '/'; // 跳转登录页
|
||||
ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {})
|
||||
ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {});
|
||||
} else {
|
||||
ElMessage({
|
||||
message: msg || '系统出错',
|
||||
type: 'error'
|
||||
})
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error(msg || 'Error'))
|
||||
return Promise.reject(new Error(msg || 'Error'));
|
||||
}
|
||||
);
|
||||
|
||||
// 导出 axios 实例
|
||||
export default service
|
||||
export default service;
|
||||
|
||||
@@ -1,61 +1,66 @@
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ref } from 'vue';
|
||||
export default function () {
|
||||
const chart = ref<any>()
|
||||
const sidebarElm = ref<Element>()
|
||||
const chart = ref<any>();
|
||||
const sidebarElm = ref<Element>();
|
||||
|
||||
const chartResizeHandler = () => {
|
||||
if (chart.value) {
|
||||
chart.value.resize()
|
||||
}
|
||||
chart.value.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const sidebarResizeHandler = (e: TransitionEvent) => {
|
||||
if (e.propertyName === 'width') {
|
||||
chartResizeHandler()
|
||||
}
|
||||
chartResizeHandler();
|
||||
}
|
||||
};
|
||||
|
||||
const initResizeEvent = () => {
|
||||
window.addEventListener('resize', chartResizeHandler)
|
||||
}
|
||||
window.addEventListener('resize', chartResizeHandler);
|
||||
};
|
||||
|
||||
const destroyResizeEvent = () => {
|
||||
window.removeEventListener('resize', chartResizeHandler)
|
||||
}
|
||||
window.removeEventListener('resize', chartResizeHandler);
|
||||
};
|
||||
|
||||
const initSidebarResizeEvent = () => {
|
||||
sidebarElm.value = document.getElementsByClassName('sidebar-container')[0]
|
||||
sidebarElm.value = document.getElementsByClassName('sidebar-container')[0];
|
||||
if (sidebarElm.value) {
|
||||
sidebarElm.value.addEventListener('transitionend', sidebarResizeHandler as EventListener)
|
||||
}
|
||||
sidebarElm.value.addEventListener(
|
||||
'transitionend',
|
||||
sidebarResizeHandler as EventListener
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const destroySidebarResizeEvent = () => {
|
||||
if (sidebarElm.value) {
|
||||
sidebarElm.value.removeEventListener('transitionend', sidebarResizeHandler as EventListener)
|
||||
}
|
||||
sidebarElm.value.removeEventListener(
|
||||
'transitionend',
|
||||
sidebarResizeHandler as EventListener
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const mounted = () => {
|
||||
initResizeEvent()
|
||||
initSidebarResizeEvent()
|
||||
}
|
||||
initResizeEvent();
|
||||
initSidebarResizeEvent();
|
||||
};
|
||||
|
||||
const beforeDestroy = () => {
|
||||
destroyResizeEvent()
|
||||
destroySidebarResizeEvent()
|
||||
}
|
||||
destroyResizeEvent();
|
||||
destroySidebarResizeEvent();
|
||||
};
|
||||
|
||||
const activated = () => {
|
||||
initResizeEvent()
|
||||
initSidebarResizeEvent()
|
||||
}
|
||||
initResizeEvent();
|
||||
initSidebarResizeEvent();
|
||||
};
|
||||
|
||||
const deactivated = () => {
|
||||
destroyResizeEvent()
|
||||
destroySidebarResizeEvent()
|
||||
}
|
||||
destroyResizeEvent();
|
||||
destroySidebarResizeEvent();
|
||||
};
|
||||
|
||||
return {
|
||||
chart,
|
||||
@@ -63,5 +68,5 @@ export default function() {
|
||||
beforeDestroy,
|
||||
activated,
|
||||
deactivated
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
|
||||
t /= d / 2
|
||||
t /= d / 2;
|
||||
if (t < 1) {
|
||||
return c / 2 * t * t + b
|
||||
return (c / 2) * t * t + b;
|
||||
}
|
||||
t--
|
||||
return -c / 2 * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
t--;
|
||||
return (-c / 2) * (t * (t - 2) - 1) + b;
|
||||
};
|
||||
|
||||
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
|
||||
const requestAnimFrame = (function () {
|
||||
return window.requestAnimationFrame || (window as any).webkitRequestAnimationFrame || (window as any).mozRequestAnimationFrame || function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
return (
|
||||
window.requestAnimationFrame ||
|
||||
(window as any).webkitRequestAnimationFrame ||
|
||||
(window as any).mozRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
})()
|
||||
);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Because it's so fucking difficult to detect the scrolling element, just move them all
|
||||
@@ -21,13 +25,17 @@ const requestAnimFrame = (function () {
|
||||
*/
|
||||
const move = (amount: number) => {
|
||||
document.documentElement.scrollTop = amount;
|
||||
(document.body.parentNode as HTMLElement).scrollTop = amount
|
||||
document.body.scrollTop = amount
|
||||
}
|
||||
(document.body.parentNode as HTMLElement).scrollTop = amount;
|
||||
document.body.scrollTop = amount;
|
||||
};
|
||||
|
||||
const position = () => {
|
||||
return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop
|
||||
}
|
||||
return (
|
||||
document.documentElement.scrollTop ||
|
||||
(document.body.parentNode as HTMLElement).scrollTop ||
|
||||
document.body.scrollTop
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} to
|
||||
@@ -35,27 +43,27 @@ const position = () => {
|
||||
* @param {Function} callback
|
||||
*/
|
||||
export const scrollTo = (to: number, duration: number, callback?: any) => {
|
||||
const start = position()
|
||||
const change = to - start
|
||||
const increment = 20
|
||||
let currentTime = 0
|
||||
duration = (typeof (duration) === 'undefined') ? 500 : duration
|
||||
const start = position();
|
||||
const change = to - start;
|
||||
const increment = 20;
|
||||
let currentTime = 0;
|
||||
duration = typeof duration === 'undefined' ? 500 : duration;
|
||||
const animateScroll = function () {
|
||||
// increment the time
|
||||
currentTime += increment
|
||||
currentTime += increment;
|
||||
// find the value with the quadratic in-out easing function
|
||||
const val = easeInOutQuad(currentTime, start, change, duration)
|
||||
const val = easeInOutQuad(currentTime, start, change, duration);
|
||||
// move the document.body
|
||||
move(val)
|
||||
move(val);
|
||||
// do the animation unless its over
|
||||
if (currentTime < duration) {
|
||||
requestAnimFrame(animateScroll)
|
||||
requestAnimFrame(animateScroll);
|
||||
} else {
|
||||
if (callback && typeof (callback) === 'function') {
|
||||
if (callback && typeof callback === 'function') {
|
||||
// the animation is done so lets callback
|
||||
callback()
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
}
|
||||
};
|
||||
animateScroll();
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isExternal(path: string) {
|
||||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path)
|
||||
return isExternal
|
||||
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
|
||||
return isExternal;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<!-- 线 + 柱混合图 -->
|
||||
<template>
|
||||
<div
|
||||
:id="id"
|
||||
:class="className"
|
||||
:style="{height, width}"
|
||||
/>
|
||||
<div :id="id" :class="className" :style="{ height, width }" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted} from "vue";
|
||||
import {init, EChartsOption} from 'echarts'
|
||||
import {
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import { init, EChartsOption } from 'echarts';
|
||||
import * as echarts from 'echarts';
|
||||
import resize from '@/utils/resize'
|
||||
import resize from '@/utils/resize';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -32,18 +34,12 @@ const props = defineProps({
|
||||
default: '200px',
|
||||
required: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
mounted,
|
||||
chart,
|
||||
beforeDestroy,
|
||||
activated,
|
||||
deactivated
|
||||
} = resize()
|
||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
||||
|
||||
function initChart() {
|
||||
const barChart = init(document.getElementById(props.id) as HTMLDivElement)
|
||||
const barChart = init(document.getElementById(props.id) as HTMLDivElement);
|
||||
|
||||
barChart.setOption({
|
||||
title: {
|
||||
@@ -111,9 +107,7 @@ function initChart() {
|
||||
{
|
||||
name: '收入',
|
||||
type: 'bar',
|
||||
data: [
|
||||
8000, 8200, 7000, 6200, 6500, 5500, 4500, 4200, 3800,
|
||||
],
|
||||
data: [8000, 8200, 7000, 6200, 6500, 5500, 4500, 4200, 3800],
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
@@ -126,9 +120,7 @@ function initChart() {
|
||||
{
|
||||
name: '毛利润',
|
||||
type: 'bar',
|
||||
data: [
|
||||
6700, 6800, 6300, 5213, 4500, 4200, 4200, 3800
|
||||
],
|
||||
data: [6700, 6800, 6300, 5213, 4500, 4200, 4200, 3800],
|
||||
barWidth: 20,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
@@ -157,27 +149,26 @@ function initChart() {
|
||||
}
|
||||
}
|
||||
]
|
||||
} as EChartsOption)
|
||||
chart.value = barChart
|
||||
} as EChartsOption);
|
||||
chart.value = barChart;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
beforeDestroy()
|
||||
})
|
||||
beforeDestroy();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
activated()
|
||||
})
|
||||
activated();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
deactivated()
|
||||
})
|
||||
deactivated();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
mounted()
|
||||
mounted();
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
})
|
||||
|
||||
initChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,16 +1,18 @@
|
||||
<!-- 漏斗图 -->
|
||||
<template>
|
||||
<div
|
||||
:id="id"
|
||||
:class="className"
|
||||
:style="{height, width}"
|
||||
/>
|
||||
<div :id="id" :class="className" :style="{ height, width }" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted} from "vue";
|
||||
import {init, EChartsOption} from 'echarts'
|
||||
import resize from '@/utils/resize'
|
||||
import {
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import { init, EChartsOption } from 'echarts';
|
||||
import resize from '@/utils/resize';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -31,18 +33,12 @@ const props = defineProps({
|
||||
default: '200px',
|
||||
required: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
mounted,
|
||||
chart,
|
||||
beforeDestroy,
|
||||
activated,
|
||||
deactivated
|
||||
} = resize()
|
||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
||||
|
||||
function initChart() {
|
||||
const funnelChart = init(document.getElementById(props.id) as HTMLDivElement)
|
||||
const funnelChart = init(document.getElementById(props.id) as HTMLDivElement);
|
||||
|
||||
funnelChart.setOption({
|
||||
title: {
|
||||
@@ -112,31 +108,28 @@ function initChart() {
|
||||
]
|
||||
}
|
||||
]
|
||||
} as EChartsOption)
|
||||
chart.value = funnelChart
|
||||
} as EChartsOption);
|
||||
chart.value = funnelChart;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
beforeDestroy()
|
||||
})
|
||||
beforeDestroy();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
activated()
|
||||
})
|
||||
activated();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
deactivated()
|
||||
})
|
||||
deactivated();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
mounted()
|
||||
mounted();
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
})
|
||||
|
||||
initChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<!-- 饼图 -->
|
||||
<template>
|
||||
<div
|
||||
:id="id"
|
||||
:class="className"
|
||||
:style="{height, width}"
|
||||
/>
|
||||
<div :id="id" :class="className" :style="{ height, width }" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted} from "vue";
|
||||
import {init, EChartsOption} from 'echarts'
|
||||
import resize from "@/utils/resize";
|
||||
import {
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import { init, EChartsOption } from 'echarts';
|
||||
import resize from '@/utils/resize';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -31,18 +33,12 @@ const props = defineProps({
|
||||
default: '200px',
|
||||
required: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
mounted,
|
||||
chart,
|
||||
beforeDestroy,
|
||||
activated,
|
||||
deactivated
|
||||
} = resize()
|
||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
||||
|
||||
function initChart() {
|
||||
const pieChart = init(document.getElementById(props.id) as HTMLDivElement)
|
||||
const pieChart = init(document.getElementById(props.id) as HTMLDivElement);
|
||||
|
||||
pieChart.setOption({
|
||||
title: {
|
||||
@@ -78,10 +74,8 @@ function initChart() {
|
||||
normal: {
|
||||
color: function (params: any) {
|
||||
//自定义颜色
|
||||
const colorList = [
|
||||
'#409EFF', '#67C23A', '#E6A23C', '#F56C6C'
|
||||
];
|
||||
return colorList[params.dataIndex]
|
||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -93,31 +87,29 @@ function initChart() {
|
||||
]
|
||||
}
|
||||
]
|
||||
} as EChartsOption)
|
||||
} as EChartsOption);
|
||||
|
||||
chart.value = pieChart
|
||||
chart.value = pieChart;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
beforeDestroy()
|
||||
})
|
||||
beforeDestroy();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
activated()
|
||||
})
|
||||
activated();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
deactivated()
|
||||
})
|
||||
deactivated();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
mounted()
|
||||
mounted();
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
})
|
||||
initChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<!-- 雷达图 -->
|
||||
<template>
|
||||
<div
|
||||
:id="id"
|
||||
:class="className"
|
||||
:style="{height, width}"
|
||||
/>
|
||||
<div :id="id" :class="className" :style="{ height, width }" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted} from "vue";
|
||||
import {init, EChartsOption} from 'echarts'
|
||||
import resize from "@/utils/resize";
|
||||
import {
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import { init, EChartsOption } from 'echarts';
|
||||
import resize from '@/utils/resize';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -31,18 +33,12 @@ const props = defineProps({
|
||||
default: '200px',
|
||||
required: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
mounted,
|
||||
chart,
|
||||
beforeDestroy,
|
||||
activated,
|
||||
deactivated
|
||||
} = resize()
|
||||
const { mounted, chart, beforeDestroy, activated, deactivated } = resize();
|
||||
|
||||
function initChart() {
|
||||
const radarChart = init(document.getElementById(props.id) as HTMLDivElement)
|
||||
const radarChart = init(document.getElementById(props.id) as HTMLDivElement);
|
||||
|
||||
radarChart.setOption({
|
||||
title: {
|
||||
@@ -89,10 +85,8 @@ function initChart() {
|
||||
normal: {
|
||||
color: function (params: any) {
|
||||
//自定义颜色
|
||||
const colorList = [
|
||||
'#409EFF', '#67C23A', '#E6A23C', '#F56C6C'
|
||||
];
|
||||
return colorList[params.dataIndex]
|
||||
const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -112,31 +106,29 @@ function initChart() {
|
||||
]
|
||||
}
|
||||
]
|
||||
} as EChartsOption)
|
||||
} as EChartsOption);
|
||||
|
||||
chart.value = radarChart
|
||||
chart.value = radarChart;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
beforeDestroy()
|
||||
})
|
||||
beforeDestroy();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
activated()
|
||||
})
|
||||
activated();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
deactivated()
|
||||
})
|
||||
deactivated();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
mounted()
|
||||
mounted();
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
})
|
||||
initChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -9,43 +9,73 @@
|
||||
<el-link target="_blank" type="primary" href="https://gitee.com/haoxr">
|
||||
youlai-mall
|
||||
</el-link>
|
||||
是基于Spring Boot 2.6、Spring Cloud 2021 &
|
||||
Alibaba 2021、Vue3、Element-Plus、uni-app等主流技术栈构建的一整套全栈开源商城项目,
|
||||
是基于Spring Boot 2.6、Spring Cloud 2021 & Alibaba
|
||||
2021、Vue3、Element-Plus、uni-app等主流技术栈构建的一整套全栈开源商城项目,
|
||||
涉及
|
||||
<el-link target="_blank" type="primary" href="https://gitee.com/youlaitech/youlai-mall">后端微服务</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="primary"
|
||||
href="https://gitee.com/youlaitech/youlai-mall"
|
||||
>后端微服务</el-link
|
||||
>
|
||||
、
|
||||
<el-link target="_blank" type="success" href="https://gitee.com/youlaitech/youlai-mall-admin">前端管理</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="success"
|
||||
href="https://gitee.com/youlaitech/youlai-mall-admin"
|
||||
>前端管理</el-link
|
||||
>
|
||||
、
|
||||
<el-link target="_blank" type="warning" href="https://gitee.com/youlaitech/youlai-mall-weapp">微信小程序
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="warning"
|
||||
href="https://gitee.com/youlaitech/youlai-mall-weapp"
|
||||
>微信小程序
|
||||
</el-link>
|
||||
和
|
||||
<el-link target="_blank" type="danger" href="https://gitee.com/youlaitech/youlai-mall-weapp">APP应用</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="danger"
|
||||
href="https://gitee.com/youlaitech/youlai-mall-weapp"
|
||||
>APP应用</el-link
|
||||
>
|
||||
等多端的开发。
|
||||
<el-divider />
|
||||
|
||||
<!-- 源码地址 -->
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6">
|
||||
<el-badge value="免费开源" class="fw-b">
|
||||
项目地址
|
||||
</el-badge>
|
||||
<el-badge value="免费开源" class="fw-b"> 项目地址 </el-badge>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-link target="_blank" type="warning" href="http://youlaitech.gitee.io/youlai-mall">官方文档(完善中..)</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="warning"
|
||||
href="http://youlaitech.gitee.io/youlai-mall"
|
||||
>官方文档(完善中..)</el-link
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-link target="_blank" type="primary" href="https://github.com/youlaitech">Github</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="primary"
|
||||
href="https://github.com/youlaitech"
|
||||
>Github</el-link
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-link target="_blank" type="success" href="https://gitee.com/youlaiorg">码云</el-link>
|
||||
<el-link
|
||||
target="_blank"
|
||||
type="success"
|
||||
href="https://gitee.com/youlaiorg"
|
||||
>码云</el-link
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider />
|
||||
<!-- 技术栈 -->
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6" class="fw-b">
|
||||
后端技术栈
|
||||
</el-col>
|
||||
<el-col :span="6" class="fw-b"> 后端技术栈 </el-col>
|
||||
<el-col :span="18">
|
||||
Spring Boot、Spring Cloud & Alibaba、Spring Security
|
||||
OAuth2、JWT、Elastic Stack 、K8s...
|
||||
@@ -53,9 +83,7 @@
|
||||
</el-row>
|
||||
<el-divider />
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6" class="fw-b">
|
||||
前端技术栈
|
||||
</el-col>
|
||||
<el-col :span="6" class="fw-b"> 前端技术栈 </el-col>
|
||||
<el-col :span="18">
|
||||
Vue3、TypeScript、Element-Plus、uni-app、vue3-element-admin ...
|
||||
</el-col>
|
||||
@@ -67,12 +95,11 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "index"
|
||||
}
|
||||
name: 'index'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.component-container {
|
||||
.project-card {
|
||||
font-size: 14px;
|
||||
@@ -89,5 +116,4 @@ export default {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -9,14 +9,28 @@
|
||||
<el-tab-pane label="开发者" name="developer">
|
||||
<div class="developer" ref="dev_wrapper">
|
||||
<ul class="developer__container">
|
||||
<li class="developer__item" v-for="(item, index) in developers" :key="index">
|
||||
<li
|
||||
class="developer__item"
|
||||
v-for="(item, index) in developers"
|
||||
:key="index"
|
||||
>
|
||||
<div class="developer__inner">
|
||||
<el-image class="developer__img" :src="item.imgUrl" :preview-src-list="[item.imgUrl]"></el-image>
|
||||
<el-image
|
||||
class="developer__img"
|
||||
:src="item.imgUrl"
|
||||
:preview-src-list="[item.imgUrl]"
|
||||
></el-image>
|
||||
<div class="developer__info">
|
||||
<span class="developer__nickname">{{ item.nickname }}</span>
|
||||
<div class="developer__position">
|
||||
<el-tag v-for="(position, i) in item.positions" :type="(colors[i % colors.length] as any)"
|
||||
:class="i !== 0 ? 'f-ml' : ''" size="small" :key="i">{{ position }}</el-tag>
|
||||
<el-tag
|
||||
v-for="(position, i) in item.positions"
|
||||
:type="(colors[i % colors.length] as any)"
|
||||
:class="i !== 0 ? 'f-ml' : ''"
|
||||
size="small"
|
||||
:key="i"
|
||||
>{{ position }}</el-tag
|
||||
>
|
||||
</div>
|
||||
<div class="developer__homepage">
|
||||
<a :href="item.homepage" target="_blank">个人主页</a>
|
||||
@@ -31,9 +45,16 @@
|
||||
|
||||
<el-tab-pane label="交流群" name="2">
|
||||
<div class="group">
|
||||
<el-image class="group-img" src="https://www.youlai.tech/files/blog/youlaiqun.png"
|
||||
:preview-src-list="['https://www.youlai.tech/files/blog/youlaiqun.png']" />
|
||||
<div class="group-tip">群二维码过期可添加开发者微信由其拉入群,备注「有来」即可。</div>
|
||||
<el-image
|
||||
class="group-img"
|
||||
src="https://www.youlai.tech/files/blog/youlaiqun.png"
|
||||
:preview-src-list="[
|
||||
'https://www.youlai.tech/files/blog/youlaiqun.png'
|
||||
]"
|
||||
/>
|
||||
<div class="group-tip">
|
||||
群二维码过期可添加开发者微信由其拉入群,备注「有来」即可。
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -52,59 +73,57 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, reactive, ref, toRefs, watchEffect } from "vue";
|
||||
import BScroll from "better-scroll";
|
||||
import { nextTick, onMounted, reactive, ref, toRefs, watchEffect } from 'vue';
|
||||
import BScroll from 'better-scroll';
|
||||
|
||||
const state = reactive({
|
||||
teamActiveName: "developer",
|
||||
teamActiveName: 'developer',
|
||||
developers: [
|
||||
{
|
||||
imgUrl: "https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg",
|
||||
nickname: "郝先瑞",
|
||||
positions: ["后端", "前端", "文档"],
|
||||
homepage: "https://www.cnblogs.com/haoxianrui/",
|
||||
imgUrl: 'https://s2.loli.net/2022/04/06/yRx8uzj4emA5QVr.jpg',
|
||||
nickname: '郝先瑞',
|
||||
positions: ['后端', '前端', '文档'],
|
||||
homepage: 'https://www.cnblogs.com/haoxianrui/'
|
||||
},
|
||||
{
|
||||
imgUrl: "https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg",
|
||||
nickname: "张川",
|
||||
positions: ["后端", "前端"],
|
||||
homepage: "https://blog.csdn.net/qq_41595149",
|
||||
imgUrl: 'https://s2.loli.net/2022/04/06/cQihGv9uPsTjXk1.jpg',
|
||||
nickname: '张川',
|
||||
positions: ['后端', '前端'],
|
||||
homepage: 'https://blog.csdn.net/qq_41595149'
|
||||
},
|
||||
{
|
||||
imgUrl: "https://s2.loli.net/2022/04/07/2IiOYBHnRGKgCSd.jpg",
|
||||
nickname: "张加林",
|
||||
positions: ["DevOps"],
|
||||
homepage: "https://gitee.com/ximy",
|
||||
imgUrl: 'https://s2.loli.net/2022/04/07/2IiOYBHnRGKgCSd.jpg',
|
||||
nickname: '张加林',
|
||||
positions: ['DevOps'],
|
||||
homepage: 'https://gitee.com/ximy'
|
||||
}
|
||||
],
|
||||
colors: ["", "success", "warning", "danger"],
|
||||
colors: ['', 'success', 'warning', 'danger'],
|
||||
indicatorImgUrl: new URL(
|
||||
`../../../../assets/index/indicator.png`,
|
||||
import.meta.url
|
||||
).href,
|
||||
).href
|
||||
});
|
||||
|
||||
const { teamActiveName, developers, colors, indicatorImgUrl } = toRefs(state);
|
||||
|
||||
let bScroll = reactive({})
|
||||
let bScroll = reactive({});
|
||||
|
||||
const dev_wrapper = ref<HTMLElement | any>(null)
|
||||
const dev_wrapper = ref<HTMLElement | any>(null);
|
||||
|
||||
onMounted(() => {
|
||||
bScroll = new BScroll(dev_wrapper.value, {
|
||||
mouseWheel: true, //开启鼠标滚轮
|
||||
disableMouse: false, //启用鼠标拖动
|
||||
scrollX: true, //X轴滚动启用
|
||||
})
|
||||
})
|
||||
scrollX: true //X轴滚动启用
|
||||
});
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
nextTick(() => {
|
||||
bScroll && (bScroll as any).refresh()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
bScroll && (bScroll as any).refresh();
|
||||
});
|
||||
});
|
||||
|
||||
// let bScroll = reactive({})
|
||||
|
||||
|
||||
@@ -86,17 +86,32 @@
|
||||
<!-- Echarts 图表 -->
|
||||
<el-row :gutter="40" style="margin-top: 20px">
|
||||
<el-col :sm="24" :lg="8" class="card-panel__col">
|
||||
<BarChart id="barChart" height="400px" width="100%" class="chart-container" />
|
||||
<BarChart
|
||||
id="barChart"
|
||||
height="400px"
|
||||
width="100%"
|
||||
class="chart-container"
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
|
||||
<PieChart id="pieChart" height="400px" width="100%" class="chart-container" />
|
||||
<PieChart
|
||||
id="pieChart"
|
||||
height="400px"
|
||||
width="100%"
|
||||
class="chart-container"
|
||||
/>
|
||||
<!--订单漏斗图-->
|
||||
<!--<FunnelChart id="funnelChart" height="400px" width="100%" class="chart-container"/>-->
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :sm="12" :lg="8" class="card-panel__col">
|
||||
<RadarChart id="radarChart" height="400px" width="100%" class="chart-container" />
|
||||
<RadarChart
|
||||
id="radarChart"
|
||||
height="400px"
|
||||
width="100%"
|
||||
class="chart-container"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -104,17 +119,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
// 组件引用
|
||||
import GithubCorner from "@/components/GithubCorner/index.vue";
|
||||
import SvgIcon from "@/components/SvgIcon/index.vue";
|
||||
import BarChart from "./components/Chart/BarChart.vue";
|
||||
import PieChart from "./components/Chart/PieChart.vue";
|
||||
import RadarChart from "./components/Chart/RadarChart.vue";
|
||||
import GithubCorner from '@/components/GithubCorner/index.vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import BarChart from './components/Chart/BarChart.vue';
|
||||
import PieChart from './components/Chart/PieChart.vue';
|
||||
import RadarChart from './components/Chart/RadarChart.vue';
|
||||
|
||||
import Project from "./components/Project/index.vue";
|
||||
import Team from "./components/Team/index.vue";
|
||||
import Project from './components/Project/index.vue';
|
||||
import Team from './components/Team/index.vue';
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-container {
|
||||
padding: 24px;
|
||||
@@ -135,7 +149,8 @@ import Team from "./components/Team/index.vue";
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
.user-name {}
|
||||
.user-name {
|
||||
}
|
||||
|
||||
.box-center {
|
||||
padding-top: 10px;
|
||||
|
||||
@@ -5,57 +5,61 @@
|
||||
</el-button>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h1 class="text-jumbo text-ginormous">
|
||||
Oops!
|
||||
</h1>
|
||||
<h1 class="text-jumbo text-ginormous">Oops!</h1>
|
||||
gif来源<a href="https://zh.airbnb.com/" target="_blank">airbnb</a> 页面
|
||||
<h2>你没有权限去该页面</h2>
|
||||
<h6>如有不满请联系你领导</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li>或者你可以去:</li>
|
||||
<li class="link-type">
|
||||
<router-link to="/dashboard">
|
||||
回首页
|
||||
</router-link>
|
||||
<router-link to="/dashboard"> 回首页 </router-link>
|
||||
</li>
|
||||
<li class="link-type">
|
||||
<a href="https://www.taobao.com/">随便看看</a>
|
||||
</li>
|
||||
<li><a href="#" @click.prevent="dialogVisible=true">点我看图</a></li>
|
||||
<li>
|
||||
<a href="#" @click.prevent="dialogVisible = true">点我看图</a>
|
||||
</li>
|
||||
</ul>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
|
||||
<img
|
||||
:src="errGif"
|
||||
width="313"
|
||||
height="428"
|
||||
alt="Girl has dropped her ice cream."
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog :visible.sync="dialogVisible" title="随便看">
|
||||
<img :src="ewizardClap" class="pan-img">
|
||||
<el-dialog v-model:visible="dialogVisible" title="随便看">
|
||||
<img :src="ewizardClap" class="pan-img" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import errGif from '@/assets/401_images/401.gif'
|
||||
import errGif from '@/assets/401_images/401.gif';
|
||||
|
||||
export default {
|
||||
name: 'Page401',
|
||||
data() {
|
||||
return {
|
||||
errGif: errGif + '?' + +new Date(),
|
||||
ewizardClap: 'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
|
||||
ewizardClap:
|
||||
'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
|
||||
dialogVisible: false
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
if (this.$route.query.noGoBack) {
|
||||
this.$router.push({ path: '/dashboard' })
|
||||
this.$router.push({ path: '/dashboard' });
|
||||
} else {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,18 +2,43 @@
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img
|
||||
class="pic-404__parent"
|
||||
src="@/assets/404_images/404.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child left"
|
||||
src="@/assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child mid"
|
||||
src="@/assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child right"
|
||||
src="@/assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">OOPS!</div>
|
||||
<div class="bullshit__info">All rights reserved
|
||||
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
|
||||
<div class="bullshit__info">
|
||||
All rights reserved
|
||||
<a
|
||||
style="color: #20a0ff"
|
||||
href="https://wallstreetcn.com"
|
||||
target="_blank"
|
||||
>wallstreetcn</a
|
||||
>
|
||||
</div>
|
||||
<div class="bullshit__headline">{{ message }}</div>
|
||||
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
|
||||
<div class="bullshit__info">
|
||||
Please check that the URL you entered is correct, or click the button
|
||||
below to return to the homepage.
|
||||
</div>
|
||||
<a href="" class="bullshit__return-home">Back to home</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,15 +46,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message() {
|
||||
return 'The webmaster said that you can not enter this page...'
|
||||
}
|
||||
return 'The webmaster said that you can not enter this page...';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- setup 无法设置组件名称,组件名称keepAlive必须 -->
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "seata"
|
||||
name: 'seata'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,14 +15,10 @@ import {
|
||||
Right,
|
||||
CircleCheckFilled,
|
||||
CircleCloseFilled
|
||||
} from "@element-plus/icons-vue";
|
||||
import {
|
||||
payOrder,
|
||||
getSeataData,
|
||||
resetSeataData
|
||||
} from "@/api/lab/seata";
|
||||
} from '@element-plus/icons-vue';
|
||||
import { payOrder, getSeataData, resetSeataData } from '@/api/lab/seata';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { SeataFormData } from "@/types";
|
||||
import { SeataFormData } from '@/types';
|
||||
|
||||
const state = reactive({
|
||||
// 保留改变前数据
|
||||
@@ -54,19 +50,22 @@ const state = reactive({
|
||||
openTx: true, // 是否开启事务
|
||||
orderEx: true // 订单修改异常
|
||||
} as SeataFormData
|
||||
})
|
||||
});
|
||||
|
||||
const { cacheSeataData, seataData, loading, submitData } = toRefs(state)
|
||||
const { cacheSeataData, seataData, loading, submitData } = toRefs(state);
|
||||
|
||||
/**
|
||||
* 订单支付(模拟)
|
||||
*/
|
||||
function handleOrderPay() {
|
||||
|
||||
// 数据校验
|
||||
if (seataData.value.stockInfo.stockNum && seataData.value.stockInfo.stockNum != 999
|
||||
|| (seataData.value.accountInfo.balance && seataData.value.accountInfo.balance != 1000000000)
|
||||
|| (seataData.value.orderInfo.status && seataData.value.orderInfo.status != 101)
|
||||
if (
|
||||
(seataData.value.stockInfo.stockNum &&
|
||||
seataData.value.stockInfo.stockNum != 999) ||
|
||||
(seataData.value.accountInfo.balance &&
|
||||
seataData.value.accountInfo.balance != 1000000000) ||
|
||||
(seataData.value.orderInfo.status &&
|
||||
seataData.value.orderInfo.status != 101)
|
||||
) {
|
||||
ElMessageBox.confirm(
|
||||
'检查到当前数据已被污染,请先重置数据后尝试提交?',
|
||||
@@ -75,24 +74,28 @@ function handleOrderPay() {
|
||||
confirmButtonText: '重置数据',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
handleDataReset()
|
||||
}).catch(() => {
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
handleDataReset();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
// 订单支付模拟提交
|
||||
|
||||
loading.value = true
|
||||
payOrder(submitData.value).then(() => {
|
||||
ElMessage.success('订单支付成功')
|
||||
}).finally(() => {
|
||||
loading.value = true;
|
||||
payOrder(submitData.value)
|
||||
.then(() => {
|
||||
ElMessage.success('订单支付成功');
|
||||
})
|
||||
.finally(() => {
|
||||
cacheSeataData.value = {
|
||||
status: seataData.value.orderInfo.status,
|
||||
stockNum: seataData.value.stockInfo.stockNum,
|
||||
balance: seataData.value.accountInfo.balance
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,18 +103,18 @@ function handleOrderPay() {
|
||||
* 加载数据
|
||||
*/
|
||||
function loadData() {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
getSeataData().then((response: any) => {
|
||||
seataData.value = response.data
|
||||
loading.value = false
|
||||
})
|
||||
seataData.value = response.data;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
*/
|
||||
function handleDataRefresh() {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
loadData();
|
||||
}
|
||||
|
||||
@@ -119,17 +122,17 @@ function handleDataRefresh() {
|
||||
* 数据重置
|
||||
*/
|
||||
function handleDataReset() {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
resetSeataData().then(() => {
|
||||
ElMessage.success('数据还原成功')
|
||||
ElMessage.success('数据还原成功');
|
||||
loading.value = false;
|
||||
cacheSeataData.value = {
|
||||
status: undefined,
|
||||
stockNum: undefined,
|
||||
balance: undefined
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -141,46 +144,61 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert type="info">
|
||||
<p style="font-size: 16px;">
|
||||
<p style="font-size: 16px">
|
||||
<b>模拟订单支付流程:</b>
|
||||
扣减商品库存 → 扣减会员余额 → 修改订单状态
|
||||
</p>
|
||||
<p style="font-size: 14px;">
|
||||
<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;">
|
||||
<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>
|
||||
<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-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-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-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 :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-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>
|
||||
@@ -188,13 +206,17 @@ onMounted(() => {
|
||||
商品信息
|
||||
</template>
|
||||
|
||||
<div style="display: flex;">
|
||||
<el-image style="width: 100px; height: 100px" :src="seataData.stockInfo.picUrl" fit="fill" />
|
||||
<div style="margin-left: 10px;">
|
||||
<div style="display: flex">
|
||||
<el-image
|
||||
style="width: 100px; height: 100px"
|
||||
:src="seataData.stockInfo.picUrl"
|
||||
fit="fill"
|
||||
/>
|
||||
<div style="margin-left: 10px">
|
||||
<el-form-item label="商品名称:">
|
||||
{{ seataData.stockInfo.name }}
|
||||
</el-form-item>
|
||||
<el-form-item label="库存数量:" style="display: flex;">
|
||||
<el-form-item label="库存数量:" style="display: flex">
|
||||
<div v-if="cacheSeataData.stockNum != null">
|
||||
{{ cacheSeataData.stockNum }} 部
|
||||
<el-icon>
|
||||
@@ -204,18 +226,29 @@ onMounted(() => {
|
||||
|
||||
{{ seataData.stockInfo.stockNum }} 部
|
||||
|
||||
<div v-if="cacheSeataData.stockNum" style="margin-left: 50px;">
|
||||
<el-link v-if="cacheSeataData.stockNum != seataData.stockInfo.stockNum" type="success"
|
||||
:underline="false" :icon="CircleCheckFilled">
|
||||
<div v-if="cacheSeataData.stockNum" style="margin-left: 50px">
|
||||
<el-link
|
||||
v-if="
|
||||
cacheSeataData.stockNum != seataData.stockInfo.stockNum
|
||||
"
|
||||
type="success"
|
||||
:underline="false"
|
||||
:icon="CircleCheckFilled"
|
||||
>
|
||||
修改成功
|
||||
</el-link>
|
||||
<el-link v-else-if="cacheSeataData.stockNum == seataData.stockInfo.stockNum"
|
||||
type="danger" :underline="false" :icon="CircleCloseFilled">
|
||||
<el-link
|
||||
v-else-if="
|
||||
cacheSeataData.stockNum == seataData.stockInfo.stockNum
|
||||
"
|
||||
type="danger"
|
||||
:underline="false"
|
||||
:icon="CircleCloseFilled"
|
||||
>
|
||||
修改失败
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -228,10 +261,13 @@ onMounted(() => {
|
||||
会员信息
|
||||
</template>
|
||||
|
||||
<div style="display: flex;">
|
||||
<el-image style="width: 100px; height: 100px" :src="seataData.accountInfo.avatarUrl"
|
||||
fit="fill" />
|
||||
<div style="margin-left: 10px;">
|
||||
<div style="display: flex">
|
||||
<el-image
|
||||
style="width: 100px; height: 100px"
|
||||
:src="seataData.accountInfo.avatarUrl"
|
||||
fit="fill"
|
||||
/>
|
||||
<div style="margin-left: 10px">
|
||||
<el-form-item label="会员昵称:">
|
||||
{{ seataData.accountInfo.nickName }}
|
||||
</el-form-item>
|
||||
@@ -244,20 +280,31 @@ onMounted(() => {
|
||||
</div>
|
||||
{{ (seataData.accountInfo.balance as any) / 100 }} 元
|
||||
|
||||
<div v-if="cacheSeataData.balance" style="margin-left: 50px;">
|
||||
<el-link v-if="cacheSeataData.balance != seataData.accountInfo.balance"
|
||||
type="success" :underline="false" :icon="CircleCheckFilled">
|
||||
<div v-if="cacheSeataData.balance" style="margin-left: 50px">
|
||||
<el-link
|
||||
v-if="
|
||||
cacheSeataData.balance != seataData.accountInfo.balance
|
||||
"
|
||||
type="success"
|
||||
:underline="false"
|
||||
:icon="CircleCheckFilled"
|
||||
>
|
||||
修改成功
|
||||
</el-link>
|
||||
<el-link v-else-if="cacheSeataData.balance == seataData.accountInfo.balance"
|
||||
type="danger" :underline="false" :icon="CircleCloseFilled">
|
||||
<el-link
|
||||
v-else-if="
|
||||
cacheSeataData.balance == seataData.accountInfo.balance
|
||||
"
|
||||
type="danger"
|
||||
:underline="false"
|
||||
:icon="CircleCloseFilled"
|
||||
>
|
||||
修改失败
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
@@ -266,8 +313,13 @@ onMounted(() => {
|
||||
<template #header>
|
||||
<svg-icon icon-class="order" />
|
||||
订单信息
|
||||
<el-checkbox v-model="submitData.orderEx" :label="true" style="float: right;color: #F56C6C;">
|
||||
搞点异常</el-checkbox>
|
||||
<el-checkbox
|
||||
v-model="submitData.orderEx"
|
||||
:label="true"
|
||||
style="float: right; color: #f56c6c"
|
||||
>
|
||||
搞点异常</el-checkbox
|
||||
>
|
||||
</template>
|
||||
<el-form-item label="订单编号:">
|
||||
{{ seataData.orderInfo.orderSn }}
|
||||
@@ -275,30 +327,39 @@ onMounted(() => {
|
||||
|
||||
<el-form-item label="订单状态:">
|
||||
<div v-if="cacheSeataData.status == 101">
|
||||
<el-tag type="info">
|
||||
待支付
|
||||
</el-tag>
|
||||
<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>
|
||||
<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">
|
||||
<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
|
||||
v-else
|
||||
type="danger"
|
||||
:underline="false"
|
||||
:icon="CircleCloseFilled"
|
||||
>
|
||||
修改失败
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
@@ -310,6 +371,5 @@ onMounted(() => {
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user