style: 全局代码格式化

Former-commit-id: bb50c8419b8fcdeb48c93fce9f399d901e8f5a52
This commit is contained in:
郝先瑞
2022-05-04 15:02:33 +08:00
parent e563bc340c
commit 11f02c0254
136 changed files with 11147 additions and 9780 deletions

View File

@@ -23,4 +23,4 @@ module.exports = {
// subject 大小写不做校验
'subject-case': [0]
}
}
};

View File

@@ -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>

View File

@@ -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'
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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
})
});
}

View File

@@ -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'
});
}

View File

@@ -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'
});
}

View File

@@ -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 }
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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'
})
});
}

View File

@@ -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 }
})
});
}

View File

@@ -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'
}
})
});
}

View File

@@ -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
View File

@@ -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 {};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -1 +1 @@
export {hasPerm,hasRole} from "./permission";
export { hasPerm, hasRole } from './permission';

View File

@@ -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
View File

@@ -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;
}

View File

@@ -21,4 +21,4 @@ export default {
document: 'Document',
gitee: 'Gitee'
}
}
};

View File

@@ -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;

View File

@@ -20,4 +20,4 @@ export default {
document: '项目文档',
gitee: '码云'
}
}
};

View File

@@ -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();

View File

@@ -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}`);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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;

View File

@@ -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');

View File

@@ -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();
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,6 +1,6 @@
@mixin clearfix {
&:after {
content: "";
content: '';
display: table;
clear: both;
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
// sidebar
$menuText: #bfcbd9;
$menuActiveText:#409EFF;
$menuActiveText: #409eff;
$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951
$menuBg: #304156;

View File

@@ -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;
}

View File

@@ -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[]>;
/**
* 订单表单类型声明

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -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[]>;
/**
* 广告表单类型声明

View File

@@ -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[]>;
/**
* 客户端表单类型声明

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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[];
}

View File

@@ -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)));
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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
}
};
}

View File

@@ -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();
};

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -9,43 +9,73 @@
<el-link target="_blank" type="primary" href="https://gitee.com/haoxr">
youlai-mall
</el-link>
是基于Spring Boot 2.6Spring Cloud 2021 &
Alibaba 2021Vue3Element-Plusuni-app等主流技术栈构建的一整套全栈开源商城项目
是基于Spring Boot 2.6Spring Cloud 2021 & Alibaba
2021Vue3Element-Plusuni-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 BootSpring Cloud & AlibabaSpring Security
OAuth2JWTElastic 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">
Vue3TypeScriptElement-Plusuni-appvue3-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>

View File

@@ -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({})

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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