Initial commit

This commit is contained in:
2026-04-23 16:58:11 +08:00
commit 267eba1eca
2582 changed files with 273338 additions and 0 deletions

49
promo-ui2/src/App.vue Normal file
View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { ConfigGlobal } from '@/components/ConfigGlobal'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('app')
const appStore = useAppStore()
const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
appStore.initTheme()
</script>
<template>
<ConfigGlobal :size="currentSize">
<RouterView :class="greyMode ? `${prefixCls}-grey-mode` : ''" />
</ConfigGlobal>
</template>
<style lang="less">
@prefix-cls: ~'@{adminNamespace}-app';
.size {
width: 100%;
height: 100%;
}
html,
body {
padding: 0 !important;
margin: 0;
overflow: hidden;
.size;
#app {
.size;
}
}
.@{prefix-cls}-grey-mode {
filter: grayscale(100%);
}
</style>

View File

@@ -0,0 +1,11 @@
import request from '@/axios'
// 获取所有字典
export const getDictApi = () => {
return request.get({ url: '/mock/dict/list' })
}
// 模拟获取某个字典
export const getDictOneApi = async () => {
return request.get({ url: '/mock/dict/one' })
}

View File

@@ -0,0 +1,23 @@
import request from '@/axios'
import type {
AnalysisTotalTypes,
UserAccessSource,
WeeklyUserActivity,
MonthlySales
} from './types'
export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
return request.get({ url: '/mock/analysis/total' })
}
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
return request.get({ url: '/mock/analysis/userAccessSource' })
}
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
return request.get({ url: '/mock/analysis/weeklyUserActivity' })
}
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
return request.get({ url: '/mock/analysis/monthlySales' })
}

View File

@@ -0,0 +1,22 @@
export type AnalysisTotalTypes = {
users: number
messages: number
moneys: number
shoppings: number
}
export type UserAccessSource = {
value: number
name: string
}
export type WeeklyUserActivity = {
value: number
name: string
}
export type MonthlySales = {
name: string
estimate: number
actual: number
}

View File

@@ -0,0 +1,22 @@
import request from '@/axios'
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
return request.get({ url: '/mock/workplace/total' })
}
export const getProjectApi = (): Promise<IResponse<Project>> => {
return request.get({ url: '/mock/workplace/project' })
}
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
return request.get({ url: '/mock/workplace/dynamic' })
}
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
return request.get({ url: '/mock/workplace/team' })
}
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
return request.get({ url: '/mock/workplace/radar' })
}

View File

@@ -0,0 +1,30 @@
export type WorkplaceTotal = {
project: number
access: number
todo: number
}
export type Project = {
name: string
icon: string
message: string
personal: string
time: Date | number | string
}
export type Dynamic = {
keys: string[]
time: Date | number | string
}
export type Team = {
name: string
icon: string
}
export type RadarData = {
personal: number
team: number
max: number
name: string
}

View File

@@ -0,0 +1,30 @@
import request from '@/axios'
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
export const getDepartmentApi = () => {
return request.get<DepartmentListResponse>({ url: '/mock/department/list' })
}
export const getUserByIdApi = (params: DepartmentUserParams) => {
return request.get<DepartmentUserResponse>({ url: '/mock/department/users', params })
}
export const deleteUserByIdApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/user/delete', data: { ids } })
}
export const saveUserApi = (data: any) => {
return request.post({ url: '/mock/department/user/save', data })
}
export const saveDepartmentApi = (data: any) => {
return request.post({ url: '/mock/department/save', data })
}
export const deleteDepartmentApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/delete', data: { ids } })
}
export const getDepartmentTableApi = (params: any) => {
return request.get({ url: '/mock/department/table/list', params })
}

View File

@@ -0,0 +1,32 @@
export interface DepartmentItem {
id: string
departmentName: string
children?: DepartmentItem[]
}
export interface DepartmentListResponse {
list: DepartmentItem[]
}
export interface DepartmentUserParams {
pageSize: number
pageIndex: number
id: string
username?: string
account?: string
}
export interface DepartmentUserItem {
id: string
username: string
account: string
email: string
createTime: string
role: string
department: DepartmentItem
}
export interface DepartmentUserResponse {
list: DepartmentUserItem[]
total: number
}

View File

@@ -0,0 +1,48 @@
import request from '@/axios'
import type { ApiSmsBodyType, RegisterUserType, UserType } from './types'
interface RoleParams {
roleName: string
}
export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/login', data })
}
export const registerApi = (data: RegisterUserType): Promise<IResponse<UserType>> => {
return request.post({ url: '/promo/register', data })
}
export const getCodeApi = (phoneNumber: string): Promise<IResponse<UserType>> => {
return request.post({
url: '/api/sms/getVerifyCode',
data: {
phoneNumber: phoneNumber,
type: '1'
}
})
}
export const loginOutApi = (): Promise<IResponse> => {
return request.get({ url: '/mock/user/loginOut' })
}
export const getUserListApi = ({ params }: AxiosConfig) => {
return request.get<{
code: string
data: {
list: UserType[]
total: number
}
}>({ url: '/mock/user/list', params })
}
export const getAdminRoleApi = (
params: RoleParams
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
return request.get({ url: '/mock/role/list', params })
}
export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
return request.get({ url: '/mock/role/list2', params })
}

View File

@@ -0,0 +1,28 @@
export interface UserLoginType {
username: string
password: string
}
export interface UserType {
uid: number
username: string
password: string
role: string
roleId: string
token: string
shareLink: string
shareDomain: string
}
export interface RegisterUserType {
username: string
password: string
check_password: string
contactInformation: string
parentInvitationCode: string
}
export interface ApiSmsBodyType {
phoneNumber: string;
type: string;
}

View File

@@ -0,0 +1,5 @@
import request from '@/axios'
export const getMenuListApi = () => {
return request.get({ url: '/mock/menu/list' })
}

View File

@@ -0,0 +1,38 @@
import request from '@/axios'
import { RequestResponse } from './types'
export const request1 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/1'
})
}
export const request2 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/2'
})
}
export const request3 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/3'
})
}
export const request4 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/4'
})
}
export const request5 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/5'
})
}
export const expired = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/expired'
})
}

View File

@@ -0,0 +1,3 @@
export interface RequestResponse {
data: string
}

View File

@@ -0,0 +1,5 @@
import request from '@/axios'
export const getRoleListApi = () => {
return request.get({ url: '/mock/role/table' })
}

View File

@@ -0,0 +1,29 @@
import request from '@/axios'
import { getPurchaseByUserIdRequest, getSubBranchesRequest } from './types'
export function getSubBranches(req: getSubBranchesRequest) {
return request.get({
url: '/promo/turnover/getSubBranches',
params: req
})
}
export function getPurchaseByUserId(req: getPurchaseByUserIdRequest) {
return request.get({
url: '/promo/turnover/getPurchaseByUserId/' + req.userId,
params: req
})
}
export function getCommissionRateByUserId(userId) {
return request.get({
url: '/promo/turnover/getCommissionRateByUserId/' + userId
})
}
export function updateCommissionRate(data) {
return request.put({
url: '/promo/turnover/updateCommissionRate',
data: data
})
}

View File

@@ -0,0 +1,14 @@
export interface getPurchaseByUserIdRequest {
pageNum: number
pageSize: number
beginTime: undefined | string
endTime: string | undefined
userId: undefined | string | number
}
export interface getSubBranchesRequest {
pageNum: number
pageSize: number
userId: undefined | string | number
playerId: undefined | string | number
}

View File

@@ -0,0 +1,26 @@
import request from '@/axios'
import type { TableData } from './types'
export const getTableListApi = (params: any) => {
return request.get({ url: '/mock/example/list', params })
}
export const getCardTableListApi = (params: any) => {
return request.get({ url: '/mock/card/list', params })
}
export const getTreeTableListApi = (params: any) => {
return request.get({ url: '/mock/example/treeList', params })
}
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
return request.post({ url: '/mock/example/save', data })
}
export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
return request.get({ url: '/mock/example/detail', params: { id } })
}
export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
return request.post({ url: '/mock/example/delete', data: { ids } })
}

View File

@@ -0,0 +1,9 @@
export type TableData = {
id: string
author: string
title: string
content: string
importance: number
display_time: string
pageviews: number
}

View File

@@ -0,0 +1,27 @@
import request from '@/axios'
export function getRealTimeData() {
return request.get({
url: '/promo/turnover/getRealTimeData'
})
}
export function getLast10DaysPromotionData() {
return request.get({
url: '/promo/turnover/getLast10DaysPromotionData'
})
}
export function getAnchorPromotionData(query) {
return request.get({
url: '/promo/turnover/getAnchorPromotionData',
params: query
})
}
export function queryPromoteWelfare(query) {
return request.get({
url: '/promo/turnover/queryPromoteWelfare',
params: query
})
}

View File

@@ -0,0 +1,33 @@
import request from '@/axios'
export function getInfo() {
return request.get({
url: '/api/getInfo',
method: 'get'
})
}
export function updateInvitationCode(data: any) {
return request.post({
url: '/promo/updateInvitationCode',
data
})
}
export function getShareDomain() {
return request.get({
url: '/promo/getsharedomain'
})
}
export function getRechargeListApi() {
return request.get({
url: '/promo/card/list'
})
}
export function generateCardListApi(id:number, num:number) {
return request.post({
url: `/promo/generateCard/${id}/${num}`
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 014.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 012.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5760" height="3040"><image width="5760" height="3040" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFoAAAAvgAQMAAAC1QKagAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEUsNEr///91v/yPAAAA AWJLR0QB/wIt3gAAAAd0SU1FB+YBBQYyN1c3BnEAAAhjSURBVHja7cExAQAAAMKg9U9tDB+gAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAACAtwFzzwABY3VrRQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMi0wMS0wNVQwNjo1 MDo1MyswMDowMCfNlVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDEtMDVUMDY6NTA6NTQrMDA6 MDCTNxNoAAAAAElFTkSuQmCC"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>

After

Width:  |  Height:  |  Size: 669 B

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>

After

Width:  |  Height:  |  Size: 731 B

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 013.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 01-3.889 2.843 10.582 10.582 0 01-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 01-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 01-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 013.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 013.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 01-3.89 2.843 11 11 0 01-4.732 1.066 10.58 10.58 0 01-4.667-1.066 12.478 12.478 0 01-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 01-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 013.824-2.772 11.212 11.212 0 014.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 01.778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 01-2.788 8.743 1236.373 1236.373 0 00-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 01-1.88-4.478 44.128 44.128 0 01-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 01-2.14-2.558 10.416 10.416 0 01-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 011.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,58 @@
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
import { ElMessage } from 'element-plus'
import qs from 'qs'
import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
import { objToFormData } from '@/utils'
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
if (
config.method === 'post' &&
config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
) {
config.data = qs.stringify(config.data)
} else if (
TRANSFORM_REQUEST_DATA &&
config.method === 'post' &&
config.headers['Content-Type'] === 'multipart/form-data' &&
!(config.data instanceof FormData)
) {
config.data = objToFormData(config.data)
}
if (config.method === 'get' && config.params) {
let url = config.url as string
url += '?'
const keys = Object.keys(config.params)
for (const key of keys) {
if (config.params[key] !== void 0 && config.params[key] !== null) {
url += `${key}=${encodeURIComponent(config.params[key])}&`
}
}
url = url.substring(0, url.length - 1)
config.params = {}
config.url = url
}
return config
}
const defaultResponseInterceptors = (response: AxiosResponse) => {
if (response?.config?.responseType === 'blob') {
// 如果是文件流,直接过
return response
} else if (response.data.code === SUCCESS_CODE || response.data.code === 200) {
return response.data
} else {
if (response?.data?.msg) {
ElMessage.error(response?.data?.msg)
} else {
ElMessage.error(response?.data?.message)
}
if (response?.data?.code === 401) {
const userStore = useUserStoreWithOut()
userStore.logout()
}
}
}
export { defaultResponseInterceptors, defaultRequestInterceptors }

View File

@@ -0,0 +1,42 @@
import service from './service'
import { CONTENT_TYPE } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
const request = (option: AxiosConfig) => {
const { url, method, params, data, headers, responseType } = option
const userStore = useUserStoreWithOut()
return service.request({
url: url,
method,
params,
data: data,
responseType: responseType,
headers: {
'Content-Type': CONTENT_TYPE,
[userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
...headers
}
})
}
export default {
get: <T = any>(option: AxiosConfig) => {
return request({ method: 'get', ...option }) as Promise<IResponse<T>>
},
post: <T = any>(option: AxiosConfig) => {
return request({ method: 'post', ...option }) as Promise<IResponse<T>>
},
delete: <T = any>(option: AxiosConfig) => {
return request({ method: 'delete', ...option }) as Promise<IResponse<T>>
},
put: <T = any>(option: AxiosConfig) => {
return request({ method: 'put', ...option }) as Promise<IResponse<T>>
},
cancelRequest: (url: string | string[]) => {
return service.cancelRequest(url)
},
cancelAllRequest: () => {
return service.cancelAllRequest()
}
}

View File

@@ -0,0 +1,76 @@
import axios, { AxiosError } from 'axios'
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
import { ElMessage } from 'element-plus'
import { REQUEST_TIMEOUT } from '@/constants'
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
const abortControllerMap: Map<string, AbortController> = new Map()
const axiosInstance: AxiosInstance = axios.create({
timeout: REQUEST_TIMEOUT,
baseURL: PATH_URL
})
axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
const controller = new AbortController()
const url = res.url || ''
res.signal = controller.signal
abortControllerMap.set(
import.meta.env.VITE_USE_MOCK === 'true' ? url.replace('/mock', '') : url,
controller
)
return res
})
axiosInstance.interceptors.response.use(
(res: AxiosResponse) => {
const url = res.config.url || ''
abortControllerMap.delete(url)
// 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
return res
},
(error: AxiosError) => {
console.log('err ' + error) // for debug
ElMessage.error(error.message)
return Promise.reject(error)
}
)
axiosInstance.interceptors.request.use(defaultRequestInterceptors)
axiosInstance.interceptors.response.use(defaultResponseInterceptors)
const service = {
request: (config: RequestConfig) => {
return new Promise((resolve, reject) => {
if (config.interceptors?.requestInterceptors) {
config = config.interceptors.requestInterceptors(config as any)
}
axiosInstance
.request(config)
.then((res) => {
resolve(res)
})
.catch((err: any) => {
reject(err)
})
})
},
cancelRequest: (url: string | string[]) => {
const urlList = Array.isArray(url) ? url : [url]
for (const _url of urlList) {
abortControllerMap.get(_url)?.abort()
abortControllerMap.delete(_url)
}
},
cancelAllRequest() {
for (const [_, controller] of abortControllerMap) {
controller.abort()
}
abortControllerMap.clear()
}
}
export default service

View File

@@ -0,0 +1,31 @@
import type {
InternalAxiosRequestConfig,
AxiosResponse,
AxiosRequestConfig,
AxiosInstance,
AxiosRequestHeaders,
AxiosError
} from 'axios'
interface RequestInterceptors<T> {
// 请求拦截
requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: (config: T) => T
responseInterceptorsCatch?: (err: any) => any
}
interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: RequestInterceptors<T>
}
export {
AxiosResponse,
RequestInterceptors,
RequestConfig,
AxiosInstance,
InternalAxiosRequestConfig,
AxiosRequestHeaders,
AxiosError
}

View File

@@ -0,0 +1,4 @@
import Avatars from './src/Avatars.vue'
export type { AvatarItem } from './src/types'
export { Avatars }

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import { ComponentSize, ElAvatar, ElTooltip } from 'element-plus'
import { PropType, computed } from 'vue'
import { AvatarItem } from './types'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('avatars')
const props = defineProps({
size: {
type: [String, Number] as PropType<ComponentSize | number>,
default: ''
},
max: {
type: Number,
default: 5
},
data: {
type: Array as PropType<AvatarItem[]>,
default: () => []
},
showTooltip: {
type: Boolean,
default: true
}
})
const filterData = computed(() => props.data.slice(0, props.max))
</script>
<template>
<div :class="prefixCls" class="flex items-center">
<template v-for="item in filterData" :key="item.url">
<template v-if="showTooltip && item.name">
<ElTooltip :content="item.name" placement="top">
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</ElTooltip>
</template>
<template v-else>
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</template>
</template>
<ElAvatar
v-if="data.length > max"
:style="{
zIndex: data.length
}"
>
<span>+{{ data.length - max }}</span>
</ElAvatar>
</div>
</template>
<style scoped lang="less">
@prefix-cls: ~'@{adminNamespace}-avatars';
.@{prefix-cls} {
.@{elNamespace}-avatar + .@{elNamespace}-avatar {
margin-left: -15px;
}
}
</style>

View File

@@ -0,0 +1,4 @@
export interface AvatarItem {
url: string
name?: string
}

View File

@@ -0,0 +1,3 @@
import Backtop from './src/Backtop.vue'
export { Backtop }

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('backtop')
</script>
<template>
<ElBacktop
:class="prefixCls"
:target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
/>
</template>

View File

@@ -0,0 +1,3 @@
import Breadcrumb from './src/Breadcrumb.vue'
export { Breadcrumb }

View File

@@ -0,0 +1,126 @@
<script lang="tsx">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('breadcrumb')
const appStore = useAppStore()
// 面包屑图标
const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
export default defineComponent({
name: 'Breadcrumb',
setup() {
const { currentRoute } = useRouter()
const { t } = useI18n()
const levelList = ref<AppRouteRecordRaw[]>([])
const permissionStore = usePermissionStore()
const menuRouters = computed(() => {
const routers = permissionStore.getRouters
return filterBreadcrumb(routers)
})
const getBreadcrumb = () => {
const currentPath = currentRoute.value.matched.slice(-1)[0].path
levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
return node.path === currentPath
})
}
const renderBreadcrumb = () => {
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
return breadcrumbList.map((v) => {
const disabled = !v.redirect || v.redirect === 'noredirect'
const meta = v.meta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
</>
) : (
t(v?.meta?.title || '')
)}
</ElBreadcrumbItem>
)
})
}
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
if (route.path.startsWith('/redirect/')) {
return
}
getBreadcrumb()
},
{
immediate: true
}
)
return () => (
<ElBreadcrumb separator="/" class={`${prefixCls} flex items-center h-full ml-[10px]`}>
<TransitionGroup appear enter-active-class="animate__animated animate__fadeInRight">
{renderBreadcrumb()}
</TransitionGroup>
</ElBreadcrumb>
)
}
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{elNamespace}-breadcrumb';
.@{prefix-cls} {
:deep(&__item) {
display: flex;
.@{prefix-cls}__inner {
display: flex;
align-items: center;
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):not(:last-child) {
.@{prefix-cls}__inner {
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):last-child {
.@{prefix-cls}__inner {
color: var(--el-text-color-placeholder);
&:hover {
color: var(--el-text-color-placeholder);
}
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
import { pathResolve } from '@/utils/routerHelper'
export const filterBreadcrumb = (
routes: AppRouteRecordRaw[],
parentPath = ''
): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const meta = route?.meta
if (meta.hidden && !meta.canTo) {
continue
}
const data: AppRouteRecordRaw =
!meta.alwaysShow && route.children?.length === 1
? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }
: { ...route }
data.path = pathResolve(parentPath, data.path)
if (data.children) {
data.children = filterBreadcrumb(data.children, data.path)
}
if (data) {
res.push(data)
}
}
return res
}

View File

@@ -0,0 +1,3 @@
import BaseButton from './src/Button.vue'
export { BaseButton }

View File

@@ -0,0 +1,121 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
import { PropType, Component, computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
const getTheme = computed(() => appStore.getTheme)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('button')
const props = defineProps({
size: {
type: String as PropType<ComponentSize>,
default: undefined
},
type: {
type: String as PropType<ButtonType>,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
plain: {
type: Boolean,
default: false
},
text: {
type: Boolean,
default: false
},
bg: {
type: Boolean,
default: false
},
link: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
},
circle: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
},
loadingIcon: {
type: [String, Object] as PropType<string | Component>,
default: undefined
},
icon: {
type: [String, Object] as PropType<string | Component>,
default: undefined
},
autofocus: {
type: Boolean,
default: false
},
nativeType: {
type: String as PropType<'button' | 'submit' | 'reset'>,
default: 'button'
},
autoInsertSpace: {
type: Boolean,
default: false
},
color: {
type: String,
default: ''
},
darker: {
type: Boolean,
default: false
},
tag: {
type: [String, Object] as PropType<string | Component>,
default: 'button'
}
})
const emits = defineEmits(['click'])
const color = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return unref(getTheme).elColorPrimary
}
return ''
})
const style = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
}
return ''
})
</script>
<template>
<ElButton
:class="`${prefixCls} color-#fff`"
v-bind="{ ...props }"
:color="color"
:style="style"
@click="() => emits('click')"
>
<slot></slot>
<slot name="icon"></slot>
<slot name="loading"></slot>
</ElButton>
</template>

View File

@@ -0,0 +1,3 @@
import CodeEditor from './src/CodeEditor.vue'
export { CodeEditor }

View File

@@ -0,0 +1,119 @@
<script setup lang="tsx">
import { useMonacoEditor } from '@/hooks/web/useMonacoEditor'
import { onMounted, computed, watch, ref } from 'vue'
import { ElSelect, ElOption, ElFormItem, ElForm } from 'element-plus'
import { languageOptions, themeOptions } from './config/config'
const props = withDefaults(
defineProps<{
width?: string | number
height?: string | number
languageSelector?: boolean
language?: string
themeSelector?: boolean
theme?: string
editorOption?: object
modelValue: string
}>(),
{
width: '100%',
height: '100%',
languageSelector: true,
language: 'javascript',
themeSelector: true,
theme: 'vs-dark',
editorOption: () => ({}),
modelValue: ''
}
)
const emits = defineEmits<{
(e: 'blur'): void
(e: 'update:modelValue', val: string): void
}>()
const monacoEditorStyle = computed(() => {
return {
width: typeof props.width === 'string' ? props.width : props.width + 'px',
height: typeof props.height === 'string' ? props.height : props.height + 'px'
}
})
const {
monacoEditorRef,
createEditor,
updateVal,
updateOptions,
getEditor,
changeLanguage,
changeTheme
} = useMonacoEditor(props.language)
onMounted(() => {
const monacoEditor = createEditor(props.editorOption)
updateMonacoVal(props.modelValue)
monacoEditor?.onDidChangeModelContent(() => {
emits('update:modelValue', monacoEditor!.getValue())
})
monacoEditor?.onDidBlurEditorText(() => {
emits('blur')
})
})
watch(
() => props.modelValue,
() => {
updateMonacoVal(props.modelValue)
}
)
const localLanguage = ref(props.language)
watch(localLanguage, (newLanguage) => {
changeLanguage(newLanguage)
})
const localTheme = ref(props.theme)
watch(localTheme, (newTheme) => {
changeTheme(newTheme)
})
function updateMonacoVal(val: string) {
if (val !== getEditor()?.getValue()) {
updateVal(val)
}
}
defineExpose({ updateOptions })
</script>
<template>
<ElForm :inline="true">
<ElFormItem v-if="languageSelector" label="language" class="w-30% mb-5px!">
<ElSelect
v-model="localLanguage"
placeholder="Please select language"
size="small"
filterable
>
<ElOption
v-for="item in languageOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="themeSelector" label="theme" class="w-30% mb-5px!">
<ElSelect v-model="localTheme" placeholder="Please select language" size="small" filterable>
<ElOption
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<div ref="monacoEditorRef" :style="monacoEditorStyle"></div>
</template>

View File

@@ -0,0 +1,129 @@
export const languageOptions = [
{ label: 'plaintext', value: 'plaintext' },
{ label: 'abap', value: 'abap' },
{ label: 'apex', value: 'apex' },
{ label: 'azcli', value: 'azcli' },
{ label: 'bat', value: 'bat' },
{ label: 'bicep', value: 'bicep' },
{ label: 'cameligo', value: 'cameligo' },
{ label: 'clojure', value: 'clojure' },
{ label: 'coffeescript', value: 'coffeescript' },
{ label: 'c', value: 'c' },
{ label: 'cpp', value: 'cpp' },
{ label: 'csharp', value: 'csharp' },
{ label: 'csp', value: 'csp' },
{ label: 'css', value: 'css' },
{ label: 'cypher', value: 'cypher' },
{ label: 'dart', value: 'dart' },
{ label: 'dockerfile', value: 'dockerfile' },
{ label: 'ecl', value: 'ecl' },
{ label: 'elixir', value: 'elixir' },
{ label: 'flow9', value: 'flow9' },
{ label: 'fsharp', value: 'fsharp' },
{ label: 'freemarker2', value: 'freemarker2' },
{
label: 'freemarker2.tag-angle.interpolation-dollar',
value: 'freemarker2.tag-angle.interpolation-dollar'
},
{
label: 'freemarker2.tag-bracket.interpolation-dollar',
value: 'freemarker2.tag-bracket.interpolation-dollar'
},
{
label: 'freemarker2.tag-angle.interpolation-bracket',
value: 'freemarker2.tag-angle.interpolation-bracket'
},
{
label: 'freemarker2.tag-bracket.interpolation-bracket',
value: 'freemarker2.tag-bracket.interpolation-bracket'
},
{
label: 'freemarker2.tag-auto.interpolation-dollar',
value: 'freemarker2.tag-auto.interpolation-dollar'
},
{
label: 'freemarker2.tag-auto.interpolation-bracket',
value: 'freemarker2.tag-auto.interpolation-bracket'
},
{ label: 'go', value: 'go' },
{ label: 'graphql', value: 'graphql' },
{ label: 'handlebars', value: 'handlebars' },
{ label: 'hcl', value: 'hcl' },
{ label: 'html', value: 'html' },
{ label: 'ini', value: 'ini' },
{ label: 'java', value: 'java' },
{ label: 'javascript', value: 'javascript' },
{ label: 'julia', value: 'julia' },
{ label: 'kotlin', value: 'kotlin' },
{ label: 'less', value: 'less' },
{ label: 'lexon', value: 'lexon' },
{ label: 'lua', value: 'lua' },
{ label: 'liquid', value: 'liquid' },
{ label: 'm3', value: 'm3' },
{ label: 'markdown', value: 'markdown' },
{ label: 'mdx', value: 'mdx' },
{ label: 'mips', value: 'mips' },
{ label: 'msdax', value: 'msdax' },
{ label: 'mysql', value: 'mysql' },
{ label: 'objective-c', value: 'objective-c' },
{ label: 'pascal', value: 'pascal' },
{ label: 'pascaligo', value: 'pascaligo' },
{ label: 'perl', value: 'perl' },
{ label: 'pgsql', value: 'pgsql' },
{ label: 'php', value: 'php' },
{ label: 'pla', value: 'pla' },
{ label: 'postiats', value: 'postiats' },
{ label: 'powerquery', value: 'powerquery' },
{ label: 'powershell', value: 'powershell' },
{ label: 'proto', value: 'proto' },
{ label: 'pug', value: 'pug' },
{ label: 'python', value: 'python' },
{ label: 'qsharp', value: 'qsharp' },
{ label: 'r', value: 'r' },
{ label: 'razor', value: 'razor' },
{ label: 'redis', value: 'redis' },
{ label: 'redshift', value: 'redshift' },
{ label: 'restructuredtext', value: 'restructuredtext' },
{ label: 'ruby', value: 'ruby' },
{ label: 'rust', value: 'rust' },
{ label: 'sb', value: 'sb' },
{ label: 'scala', value: 'scala' },
{ label: 'scheme', value: 'scheme' },
{ label: 'scss', value: 'scss' },
{ label: 'shell', value: 'shell' },
{ label: 'sol', value: 'sol' },
{ label: 'aes', value: 'aes' },
{ label: 'sparql', value: 'sparql' },
{ label: 'sql', value: 'sql' },
{ label: 'st', value: 'st' },
{ label: 'swift', value: 'swift' },
{ label: 'systemverilog', value: 'systemverilog' },
{ label: 'verilog', value: 'verilog' },
{ label: 'tcl', value: 'tcl' },
{ label: 'twig', value: 'twig' },
{ label: 'typescript', value: 'typescript' },
{ label: 'vb', value: 'vb' },
{ label: 'wgsl', value: 'wgsl' },
{ label: 'xml', value: 'xml' },
{ label: 'yaml', value: 'yaml' },
{ label: 'json', value: 'json' }
]
export const themeOptions = [
{
label: 'vs',
value: 'vs'
},
{
label: 'vs-dark',
value: 'vs-dark'
},
{
label: 'hc-black',
value: 'hc-black'
},
{
label: 'hc-light',
value: 'hc-light'
}
]

View File

@@ -0,0 +1,3 @@
import Collapse from './src/Collapse.vue'
export { Collapse }

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import { computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('collapse')
defineProps({
color: propTypes.string.def('')
})
const appStore = useAppStore()
const collapse = computed(() => appStore.getCollapse)
const toggleCollapse = () => {
const collapsed = unref(collapse)
appStore.setCollapse(!collapsed)
}
</script>
<template>
<div :class="prefixCls" @click="toggleCollapse">
<Icon
:size="18"
:icon="collapse ? 'vi-ant-design:menu-unfold-outlined' : 'vi-ant-design:menu-fold-outlined'"
:color="color"
class="cursor-pointer"
/>
</div>
</template>

View File

@@ -0,0 +1,5 @@
import ConfigGlobal from './src/ConfigGlobal.vue'
export type { ConfigGlobalTypes } from './src/types'
export { ConfigGlobal }

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { ComponentSize, ElConfigProvider } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
import { useWindowSize } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { setCssVar } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign'
const { variables } = useDesign()
const appStore = useAppStore()
const props = defineProps({
size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
// 初始化所有主题色
onMounted(() => {
appStore.setCssVarTheme()
})
const { width } = useWindowSize()
// 监听窗口变化
watch(
() => width.value,
(width: number) => {
if (width < 768) {
!appStore.getMobile ? appStore.setMobile(true) : undefined
setCssVar('--left-menu-min-width', '0')
appStore.setCollapse(true)
appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined
} else {
appStore.getMobile ? appStore.setMobile(false) : undefined
setCssVar('--left-menu-min-width', '64px')
}
},
{
immediate: true
}
)
// 多语言相关
const localeStore = useLocaleStore()
const currentLocale = computed(() => localeStore.currentLocale)
</script>
<template>
<ElConfigProvider
:namespace="variables.elNamespace"
:locale="currentLocale.elLocale"
:message="{ max: 1 }"
:size="size"
>
<slot></slot>
</ElConfigProvider>
</template>

View File

@@ -0,0 +1,5 @@
import { ComponentSize } from 'element-plus'
export interface ConfigGlobalTypes {
size?: ComponentSize
}

View File

@@ -0,0 +1,3 @@
import ContentDetailWrap from './src/ContentDetailWrap.vue'
export { ContentDetailWrap }

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { ElCard } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('content-detail-wrap')
defineProps({
title: propTypes.string.def(''),
message: propTypes.string.def('')
})
</script>
<template>
<div :class="[`${prefixCls}-container`, 'relative']">
<ElCard :class="[`${prefixCls}-body`, 'mb-20px']" shadow="never">
<div class="mb-20px pb-20px" style="border-bottom: 1px solid var(--el-border-color)">
<slot name="header"></slot>
</div>
<slot></slot>
</ElCard>
</div>
</template>

View File

@@ -0,0 +1,3 @@
import ContentWrap from './src/ContentWrap.vue'
export { ContentWrap }

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { ElCard, ElTooltip } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('content-wrap')
defineProps({
title: propTypes.string.def(''),
message: propTypes.string.def('')
})
</script>
<template>
<ElCard :class="[prefixCls]" shadow="never">
<template v-if="title" #header>
<div class="flex items-center">
<span class="text-16px font-700">{{ title }}</span>
<ElTooltip v-if="message" effect="dark" placement="right">
<template #content>
<div class="max-w-200px">{{ message }}</div>
</template>
<Icon class="ml-5px" icon="vi-bi:question-circle-fill" :size="14" />
</ElTooltip>
<div class="flex pl-20px flex-grow">
<slot name="header"></slot>
</div>
</div>
</template>
<div>
<slot></slot>
</div>
</ElCard>
</template>

View File

@@ -0,0 +1,12 @@
import ContextMenu from './src/ContextMenu.vue'
import { ElDropdown } from 'element-plus'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
export type { ContextMenuSchema } from './src/types'
export interface ContextMenuExpose {
elDropdownMenuRef: ComponentRef<typeof ElDropdown>
tagItem: RouteLocationNormalizedLoaded
}
export { ContextMenu }

View File

@@ -0,0 +1,72 @@
<script setup lang="ts">
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import { PropType, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useDesign } from '@/hooks/web/useDesign'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { ContextMenuSchema } from './types'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('context-menu')
const { t } = useI18n()
const emit = defineEmits(['visibleChange'])
const props = defineProps({
schema: {
type: Array as PropType<ContextMenuSchema[]>,
default: () => []
},
trigger: {
type: String as PropType<'click' | 'hover' | 'focus' | 'contextmenu'>,
default: 'contextmenu'
},
tagItem: {
type: Object as PropType<RouteLocationNormalizedLoaded>,
default: () => ({})
}
})
const command = (item: ContextMenuSchema) => {
item.command && item.command(item)
}
const visibleChange = (visible: boolean) => {
emit('visibleChange', visible, props.tagItem)
}
const elDropdownMenuRef = ref<ComponentRef<typeof ElDropdown>>()
defineExpose({
elDropdownMenuRef,
tagItem: props.tagItem
})
</script>
<template>
<ElDropdown
ref="elDropdownMenuRef"
:class="prefixCls"
:trigger="trigger"
placement="bottom-start"
@command="command"
@visible-change="visibleChange"
popper-class="v-context-menu-popper"
>
<slot></slot>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
v-for="(item, index) in schema"
:key="`dropdown${index}`"
:divided="item.divided"
:disabled="item.disabled"
:command="item"
>
<Icon :icon="item.icon" /> {{ t(item.label) }}
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</template>

View File

@@ -0,0 +1,7 @@
export interface ContextMenuSchema {
disabled?: boolean
divided?: boolean
icon?: string
label: string
command?: (item: ContextMenuSchema) => void
}

View File

@@ -0,0 +1,3 @@
import CountTo from './src/CountTo.vue'
export { CountTo }

View File

@@ -0,0 +1,180 @@
<script setup lang="ts">
import { reactive, computed, watch, onMounted, unref, toRef, PropType } from 'vue'
import { isNumber } from '@/utils/is'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('count-to')
const props = defineProps({
startVal: propTypes.number.def(0),
endVal: propTypes.number.def(2021),
duration: propTypes.number.def(3000),
autoplay: propTypes.bool.def(true),
decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
decimal: propTypes.string.def('.'),
separator: propTypes.string.def(','),
prefix: propTypes.string.def(''),
suffix: propTypes.string.def(''),
useEasing: propTypes.bool.def(true),
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
}
}
})
const emit = defineEmits(['mounted', 'callback'])
const formatNumber = (num: number | string) => {
const { decimals, decimal, separator, suffix, prefix } = props
num = Number(num).toFixed(decimals)
num += ''
const x = num.split('.')
let x1 = x[0]
const x2 = x.length > 1 ? decimal + x[1] : ''
const rgx = /(\d+)(\d{3})/
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2')
}
}
return prefix + x1 + x2 + suffix
}
const state = reactive<{
localStartVal: number
printVal: number | null
displayValue: string
paused: boolean
localDuration: number | null
startTime: number | null
timestamp: number | null
rAF: any
remaining: number | null
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
})
const displayValue = toRef(state, 'displayValue')
onMounted(() => {
if (props.autoplay) {
start()
}
emit('mounted')
})
const getCountDown = computed(() => {
return props.startVal > props.endVal
})
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start()
}
})
const start = () => {
const { startVal, duration } = props
state.localStartVal = startVal
state.startTime = null
state.localDuration = duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
const pauseResume = () => {
if (state.paused) {
resume()
state.paused = false
} else {
pause()
state.paused = true
}
}
const pause = () => {
cancelAnimationFrame(state.rAF)
}
const resume = () => {
state.startTime = null
state.localDuration = +(state.remaining as number)
state.localStartVal = +(state.printVal as number)
requestAnimationFrame(count)
}
const reset = () => {
state.startTime = null
cancelAnimationFrame(state.rAF)
state.displayValue = formatNumber(props.startVal)
}
const count = (timestamp: number) => {
const { useEasing, easingFn, endVal } = props
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = (state.localDuration as number) - progress
if (useEasing) {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
} else {
state.printVal = easingFn(
progress,
state.localStartVal,
endVal - state.localStartVal,
state.localDuration as number
)
}
} else {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
(state.localStartVal - endVal) * (progress / (state.localDuration as number))
} else {
state.printVal =
state.localStartVal +
(endVal - state.localStartVal) * (progress / (state.localDuration as number))
}
}
if (unref(getCountDown)) {
state.printVal = state.printVal < endVal ? endVal : state.printVal
} else {
state.printVal = state.printVal > endVal ? endVal : state.printVal
}
state.displayValue = formatNumber(state.printVal!)
if (progress < (state.localDuration as number)) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}
defineExpose({
pauseResume,
reset,
start,
pause
})
</script>
<template>
<span :class="prefixCls">
{{ displayValue }}
</span>
</template>

View File

@@ -0,0 +1,5 @@
import Descriptions from './src/Descriptions.vue'
export type { DescriptionsSchema } from './src/types'
export { Descriptions }

View File

@@ -0,0 +1,197 @@
<script lang="tsx">
import { ElCollapseTransition, ElTooltip, ElRow, ElCol } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
import { ref, unref, PropType, computed, defineComponent } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { DescriptionsSchema } from './types'
import { Icon } from '@/components/Icon'
import { get } from 'lodash-es'
const appStore = useAppStore()
const mobile = computed(() => appStore.getMobile)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('descriptions')
const defaultData = '-'
export default defineComponent({
name: 'Descriptions',
props: {
title: propTypes.string.def(''),
message: propTypes.string.def(''),
collapse: propTypes.bool.def(true),
border: propTypes.bool.def(true),
column: propTypes.number.def(2),
size: propTypes.oneOf(['large', 'default', 'small']).def('default'),
direction: propTypes.oneOf(['horizontal', 'vertical']).def('horizontal'),
extra: propTypes.string.def(''),
schema: {
type: Array as PropType<DescriptionsSchema[]>,
default: () => []
},
data: {
type: Object as PropType<any>,
default: () => ({})
}
},
setup(props, { attrs }) {
const getBindValue = computed((): any => {
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
if (unref(mobile)) {
obj.direction = 'vertical'
}
return obj
})
const getBindItemValue = (item: DescriptionsSchema) => {
const delArr: string[] = ['field']
const obj = { ...item }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return {
labelClassName: `${prefixCls}-label`,
...obj
}
}
// 折叠
const show = ref(true)
const toggleClick = () => {
if (props.collapse) {
show.value = !unref(show)
}
}
return () => {
return (
<div
class={[
prefixCls,
'bg-[var(--el-color-white)] dark:bg-[var(--el-bg-color)] dark:border-[var(--el-border-color)] dark:border-1px'
]}
>
{props.title ? (
<div
class={[
`${prefixCls}-header`,
'relative h-50px flex justify-between items-center layout-border__bottom px-10px cursor-pointer'
]}
onClick={toggleClick}
>
<div class={[`${prefixCls}-header__title`, 'relative font-18px font-bold ml-10px']}>
<div class="flex items-center">
{props.title}
{props.message ? (
<ElTooltip content={props.message} placement="right">
<Icon icon="vi-bi:question-circle-fill" class="ml-5px" size={14} />
</ElTooltip>
) : null}
</div>
</div>
{props.collapse ? (
<Icon icon={show.value ? 'vi-ep:arrow-down' : 'vi-ep:arrow-up'} />
) : null}
</div>
) : null}
<ElCollapseTransition>
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
<ElRow
gutter={0}
{...unref(getBindValue)}
class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
>
{props.schema.map((item) => {
return (
<ElCol
key={item.field}
span={item.span || 24 / props.column}
class="flex items-stretch"
>
{props.direction === 'horizontal' ? (
<div class="flex items-stretch bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
<div
{...getBindItemValue(item)}
class="w-120px text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-r-1px border-r-[var(--el-border-color-lighter)] border-r-solid "
>
{item.label}
</div>
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
{item.slots?.default
? item.slots?.default(props.data)
: (get(props.data, item.field) ?? defaultData)}
</div>
</div>
) : (
<div class="bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
<div
{...getBindItemValue(item)}
class="text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-b-1px border-b-[var(--el-border-color-lighter)] border-b-solid"
>
{item.label}
</div>
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
{item.slots?.default
? item.slots?.default(props.data)
: (get(props.data, item.field) ?? defaultData)}
</div>
</div>
)}
</ElCol>
)
})}
</ElRow>
</div>
</ElCollapseTransition>
</div>
)
}
}
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{adminNamespace}-descriptions';
:deep(.@{elNamespace}-descriptions__header) {
display: none !important;
}
.@{prefix-cls}-header {
&__title {
&::after {
position: absolute;
top: 3px;
left: -10px;
width: 4px;
height: 70%;
background: var(--el-color-primary);
content: '';
}
}
}
:deep(.@{prefix-cls}-label) {
width: 150px !important;
}
// .@{prefix-cls}-content {
// :deep(.@{elNamespace}-descriptions__cell) {
// width: 0;
// }
// }
</style>

View File

@@ -0,0 +1,15 @@
export interface DescriptionsSchema {
span?: number // 占多少分
field: string // 字段名
label?: string // label名
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
labelAlign?: 'left' | 'center' | 'right'
className?: string
labelClassName?: string
slots?: {
default?: (...args: any[]) => JSX.Element | null
label?: (...args: any[]) => JSX.Element | null
}
}

View File

@@ -0,0 +1,178 @@
import { ref } from 'vue'
export const useResize = (props?: {
minHeightPx?: number
minWidthPx?: number
initHeight?: number
initWidth?: number
}) => {
const {
minHeightPx = 400,
minWidthPx = window.innerWidth / 2,
initHeight = 400,
initWidth = window.innerWidth / 2
} = props || {}
// 屏幕宽度的 50% 作为最小宽度
// const minWidthPx = window.innerWidth / 2
// 固定的最小高度 400px
// const minHeightPx = 400
// 初始高度限制为 400px
const maxHeight = ref(initHeight + 'px')
// 初始宽度限制为 50%
const minWidth = ref(initWidth + 'px')
const setupDrag = (elDialog: any, el: any) => {
// 获取对话框元素
// 是否正在调整大小的标志
let isResizing = false
// 当前调整的方向
let currentResizeDirection = ''
// 鼠标移动时的事件处理器,用于检测鼠标位置并设置相应的光标样式
const handleMouseMove = (e: any) => {
const rect = elDialog.getBoundingClientRect()
// 鼠标相对于对话框左侧的偏移量
const offsetX = e.clientX - rect.left
// 鼠标相对于对话框顶部的偏移量
const offsetY = e.clientY - rect.top
const width = elDialog.clientWidth
const height = elDialog.clientHeight
// 获取对话框的内边距
const computedStyle = window.getComputedStyle(elDialog)
const paddingLeft = parseFloat(computedStyle.paddingLeft)
const paddingRight = parseFloat(computedStyle.paddingRight)
const paddingBottom = parseFloat(computedStyle.paddingBottom)
const paddingTop = parseFloat(computedStyle.paddingTop)
// 根据鼠标位置设置相应的光标样式和调整方向
if (!isResizing) {
if (offsetX < paddingLeft && offsetY > paddingTop && offsetY < height - paddingBottom) {
elDialog.style.cursor = 'ew-resize' // 左右箭头
currentResizeDirection = 'left'
} else if (
offsetX > width - paddingRight &&
offsetY > paddingTop &&
offsetY < height - paddingBottom
) {
elDialog.style.cursor = 'ew-resize' // 左右箭头
currentResizeDirection = 'right'
} else if (
offsetY < paddingTop &&
offsetX > paddingLeft &&
offsetX < width - paddingRight
) {
elDialog.style.cursor = 'ns-resize' // 上下箭头
currentResizeDirection = 'top'
} else if (
offsetY > height - paddingBottom &&
offsetX > paddingLeft &&
offsetX < width - paddingRight
) {
elDialog.style.cursor = 'ns-resize' // 上下箭头
currentResizeDirection = 'bottom'
} else if (offsetX < paddingLeft && offsetY < paddingTop) {
elDialog.style.cursor = 'nwse-resize' // 左上右下箭头
currentResizeDirection = 'top-left'
} else if (offsetX > width - paddingRight && offsetY < paddingTop) {
elDialog.style.cursor = 'nesw-resize' // 右上左下箭头
currentResizeDirection = 'top-right'
} else if (offsetX < paddingLeft && offsetY > height - paddingBottom) {
elDialog.style.cursor = 'nesw-resize' // 右上左下箭头
currentResizeDirection = 'bottom-left'
} else if (offsetX > width - paddingRight && offsetY > height - paddingBottom) {
elDialog.style.cursor = 'nwse-resize' // 左上右下箭头
currentResizeDirection = 'bottom-right'
} else {
elDialog.style.cursor = 'default'
currentResizeDirection = ''
}
}
}
// 鼠标按下时的事件处理器,开始调整对话框大小
const handleMouseDown = (e) => {
if (currentResizeDirection) {
isResizing = true
const initialX = e.clientX
const initialY = e.clientY
const initialWidth = elDialog.clientWidth
const initialHeight = el.querySelector('.el-dialog__body').clientHeight
// 调整大小的事件处理器
const handleResizing = (e: any) => {
if (!isResizing) return
let newWidth = initialWidth
let newHeight = initialHeight
// 根据当前调整方向计算新的宽度和高度
if (currentResizeDirection.includes('right')) {
newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
}
if (currentResizeDirection.includes('left')) {
newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
}
if (currentResizeDirection.includes('bottom')) {
newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
if (currentResizeDirection.includes('top')) {
newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
if (currentResizeDirection === 'top-left') {
newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
if (currentResizeDirection === 'top-right') {
newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
if (currentResizeDirection === 'bottom-left') {
newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
if (currentResizeDirection === 'bottom-right') {
newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
minWidth.value = `${newWidth}px`
newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
}
}
// 停止调整大小的事件处理器
const stopResizing = () => {
isResizing = false
document.removeEventListener('mousemove', handleResizing)
document.removeEventListener('mouseup', stopResizing)
}
document.addEventListener('mousemove', handleResizing)
document.addEventListener('mouseup', stopResizing)
}
}
elDialog.addEventListener('mousemove', handleMouseMove)
elDialog.addEventListener('mousedown', handleMouseDown)
}
return {
setupDrag,
maxHeight,
minWidth
}
}

View File

@@ -0,0 +1,3 @@
import Dialog from './src/Dialog.vue'
export { Dialog }

View File

@@ -0,0 +1,145 @@
<script setup lang="ts">
import { ElDialog, ElScrollbar } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { computed, useAttrs, ref, unref, useSlots, watch, nextTick } from 'vue'
import { isNumber } from '@/utils/is'
const slots = useSlots()
const props = defineProps({
modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
maxHeight: propTypes.oneOfType([String, Number]).def('400px')
})
const getBindValue = computed(() => {
const delArr: string[] = ['fullscreen', 'title', 'maxHeight']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
const isFullscreen = ref(false)
const toggleFull = () => {
isFullscreen.value = !unref(isFullscreen)
}
const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
watch(
() => isFullscreen.value,
async (val: boolean) => {
await nextTick()
if (val) {
const windowHeight = document.documentElement.offsetHeight
dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
} else {
dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
}
},
{
immediate: true
}
)
watch(
() => props.maxHeight,
(val) => {
dialogHeight.value = isNumber(val) ? `${val}px` : val
}
)
const dialogStyle = computed(() => {
return {
height: unref(dialogHeight)
}
})
</script>
<template>
<ElDialog
v-bind="getBindValue"
:fullscreen="isFullscreen"
destroy-on-close
lock-scroll
draggable
top="0"
:close-on-click-modal="false"
:show-close="false"
>
<template #header="{ close }">
<div class="flex justify-between items-center h-54px pl-15px pr-15px relative">
<slot name="title">
{{ title }}
</slot>
<div
class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]"
>
<Icon
v-if="fullscreen"
class="cursor-pointer is-hover !h-54px mr-10px"
:icon="
isFullscreen ? 'vi-radix-icons:exit-full-screen' : 'vi-radix-icons:enter-full-screen'
"
color="var(--el-color-info)"
hover-color="var(--el-color-primary)"
@click="toggleFull"
/>
<Icon
class="cursor-pointer is-hover !h-54px"
icon="vi-ep:close"
hover-color="var(--el-color-primary)"
color="var(--el-color-info)"
@click="close"
/>
</div>
</div>
</template>
<ElScrollbar :style="dialogStyle">
<slot></slot>
</ElScrollbar>
<template v-if="slots.footer" #footer>
<slot name="footer"></slot>
</template>
</ElDialog>
</template>
<style lang="less">
.@{elNamespace}-overlay-dialog {
display: flex;
justify-content: center;
align-items: center;
}
.@{elNamespace}-dialog {
margin: 0 !important;
&__header {
height: 54px;
padding: 0;
margin-right: 0 !important;
border-bottom: 1px solid var(--el-border-color);
}
&__body {
padding: 15px !important;
}
&__footer {
border-top: 1px solid var(--el-border-color);
}
&__headerbtn {
top: 0;
}
}
</style>

View File

@@ -0,0 +1,73 @@
<script lang="tsx" setup>
import { propTypes } from '@/utils/propTypes'
import { computed, getCurrentInstance, onMounted, unref, useAttrs, useSlots } from 'vue'
import Dialog from './Dialog.vue'
import { useResize } from '../hooks/useResize'
const props = defineProps({
modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
initWidth: propTypes.number.def(window.innerWidth / 2),
initHeight: propTypes.number.def(200),
minResizeWidth: propTypes.number.def(window.innerWidth / 2),
minResizeHeight: propTypes.number.def(200)
})
const { maxHeight, minWidth, setupDrag } = useResize({
minHeightPx: props.minResizeHeight,
minWidthPx: props.minResizeWidth,
initHeight: props.initHeight,
initWidth: props.initWidth
})
const vResize = {
mounted(el) {
const observer = new MutationObserver(() => {
const elDialog = el.querySelector('.el-dialog')
if (elDialog) {
// 在确认 `elDialog` 已渲染后进行处理
setupDrag(elDialog, el)
// observer.disconnect() // 一旦获取到元素,停止观察
}
})
// 开始观察子节点的变化
observer.observe(el, { childList: true, subtree: true })
}
}
const attrs = useAttrs()
const slots = useSlots()
const getBindValue = computed(() => {
const delArr: string[] = ['maxHeight', 'width']
const obj = Object.assign({}, { ...unref(attrs), ...props })
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
const instance = getCurrentInstance()
const initDirective = () => {
const directives = instance?.appContext?.app._context?.directives
// 检查指令是否已经注册
if (!directives || !directives['resize']) {
instance?.appContext?.app.directive('resize', vResize)
}
}
onMounted(() => {
initDirective()
})
</script>
<template>
<div v-resize>
<Dialog v-bind="getBindValue" :maxHeight="maxHeight" :width="minWidth">
<slot></slot>
<template v-if="slots.footer" #footer>
<slot name="footer"></slot>
</template>
</Dialog>
</div>
</template>

View File

@@ -0,0 +1,3 @@
import Echart from './src/Echart.vue'
export { Echart }

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import type { EChartsOption } from 'echarts'
import echarts from '@/plugins/echarts'
import { debounce } from 'lodash-es'
import 'echarts-wordcloud'
import { propTypes } from '@/utils/propTypes'
import { computed, PropType, ref, unref, watch, onMounted, onBeforeUnmount, onActivated } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { isString } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('echart')
const appStore = useAppStore()
const props = defineProps({
options: {
type: Object as PropType<EChartsOption>,
required: true
},
width: propTypes.oneOfType([Number, String]).def('100%'),
height: propTypes.oneOfType([Number, String]).def('500px')
})
const isDark = computed(() => appStore.getIsDark)
const theme = computed(() => {
const echartTheme: boolean | string = unref(isDark) ? true : 'auto'
return echartTheme
})
const options = computed(() => {
return Object.assign(props.options, {
darkMode: unref(theme)
})
})
const elRef = ref<ElRef>()
let echartRef: Nullable<echarts.ECharts> = null
const contentEl = ref<Element>()
const styles = computed(() => {
const width = isString(props.width) ? props.width : `${props.width}px`
const height = isString(props.height) ? props.height : `${props.height}px`
return {
width,
height
}
})
const initChart = () => {
if (unref(elRef) && props.options) {
echartRef = echarts.init(unref(elRef) as HTMLElement)
echartRef?.setOption(unref(options))
}
}
watch(
() => options.value,
(options) => {
if (echartRef) {
echartRef?.setOption(options)
}
},
{
deep: true
}
)
const resizeHandler = debounce(() => {
if (echartRef) {
echartRef.resize()
}
}, 100)
const contentResizeHandler = async (e: TransitionEvent) => {
if (e.propertyName === 'width') {
resizeHandler()
}
}
onMounted(() => {
setTimeout(() => {
initChart()
}, 0)
window.addEventListener('resize', resizeHandler)
contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
unref(contentEl) &&
(unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler)
unref(contentEl) &&
(unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
})
onActivated(() => {
if (echartRef) {
echartRef.resize()
}
})
</script>
<template>
<div ref="elRef" :class="[$attrs.class, prefixCls]" :style="styles"></div>
</template>

View File

@@ -0,0 +1,8 @@
import Editor from './src/Editor.vue'
import { IDomEditor } from '@wangeditor/editor'
export interface EditorExpose {
getEditorRef: () => Promise<IDomEditor>
}
export { Editor }

View File

@@ -0,0 +1,135 @@
<script setup lang="ts">
import { onBeforeUnmount, computed, PropType, unref, nextTick, ref, watch, shallowRef } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { IDomEditor, IEditorConfig, i18nChangeLanguage } from '@wangeditor/editor'
import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is'
import { ElMessage } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
const localeStore = useLocaleStore()
const currentLocale = computed(() => localeStore.getCurrentLocale)
i18nChangeLanguage(unref(currentLocale).lang)
const props = defineProps({
editorId: propTypes.string.def('wangeEditor-1'),
height: propTypes.oneOfType([Number, String]).def('500px'),
editorConfig: {
type: Object as PropType<IEditorConfig>,
default: () => undefined
},
modelValue: propTypes.string.def('')
})
const emit = defineEmits(['change', 'update:modelValue'])
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef<IDomEditor>()
const valueHtml = ref('')
watch(
() => props.modelValue,
(val: string) => {
if (val === unref(valueHtml)) return
valueHtml.value = val
}
)
// 监听
watch(
() => valueHtml.value,
(val: string) => {
emit('update:modelValue', val)
}
)
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
valueHtml.value = props.modelValue
}
// 编辑器配置
const editorConfig = computed((): IEditorConfig => {
return Object.assign(
{
readOnly: false,
customAlert: (s: string, t: string) => {
switch (t) {
case 'success':
ElMessage.success(s)
break
case 'info':
ElMessage.info(s)
break
case 'warning':
ElMessage.warning(s)
break
case 'error':
ElMessage.error(s)
break
default:
ElMessage.info(s)
break
}
},
autoFocus: false,
scroll: true,
uploadImgShowBase64: true
},
props.editorConfig || {}
)
})
const editorStyle = computed(() => {
return {
height: isNumber(props.height) ? `${props.height}px` : props.height
}
})
// 回调函数
const handleChange = (editor: IDomEditor) => {
emit('change', editor)
}
// 组件销毁时,及时销毁编辑器
onBeforeUnmount(() => {
const editor = unref(editorRef.value)
// 销毁,并移除 editor
editor?.destroy()
})
const getEditorRef = async (): Promise<IDomEditor> => {
await nextTick()
return unref(editorRef.value) as IDomEditor
}
defineExpose({
getEditorRef
})
</script>
<template>
<div class="border-1 border-solid border-[var(--el-border-color)] z-10">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:editorId="editorId"
class="border-0 b-b-1 border-solid border-[var(--el-border-color)]"
/>
<!-- 编辑器 -->
<Editor
v-model="valueHtml"
:editorId="editorId"
:defaultConfig="editorConfig"
:style="editorStyle"
@on-change="handleChange"
@on-created="handleCreated"
/>
</div>
</template>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@@ -0,0 +1,3 @@
import Error from './src/Error.vue'
export { Error }

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
import pageError from '@/assets/svgs/404.svg'
import networkError from '@/assets/svgs/500.svg'
import noPermission from '@/assets/svgs/403.svg'
import { propTypes } from '@/utils/propTypes'
import { useI18n } from '@/hooks/web/useI18n'
interface ErrorMap {
url: string
message: string
buttonText: string
}
const { t } = useI18n()
const errorMap: {
[key: string]: ErrorMap
} = {
'404': {
url: pageError,
message: t('error.pageError'),
buttonText: t('error.returnToHome')
},
'500': {
url: networkError,
message: t('error.networkError'),
buttonText: t('error.returnToHome')
},
'403': {
url: noPermission,
message: t('error.noPermission'),
buttonText: t('error.returnToHome')
}
}
const props = defineProps({
type: propTypes.string.validate((v: string) => ['404', '500', '403'].includes(v)).def('404')
})
const emit = defineEmits(['errorClick'])
const btnClick = () => {
emit('errorClick', props.type)
}
</script>
<template>
<div class="flex justify-center">
<div class="text-center">
<img width="350" :src="errorMap[type].url" alt="" />
<div class="text-14px text-[var(--el-color-info)]">{{ errorMap[type].message }}</div>
<div class="mt-20px">
<BaseButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</BaseButton>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,3 @@
import Footer from './src/Footer.vue'
export { Footer }

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
import { computed } from 'vue'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('footer')
const appStore = useAppStore()
const title = computed(() => appStore.getTitle)
</script>
<template>
<div
:class="prefixCls"
class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)] overflow-hidden"
>
Copyright ©2021-present {{ title }}
</div>
</template>

View File

@@ -0,0 +1,48 @@
import Form from './src/Form.vue'
import type { FormSchema, FormSetProps } from './src/types'
export type {
ComponentNameEnum,
ComponentName,
InputComponentProps,
AutocompleteComponentProps,
InputNumberComponentProps,
SelectOption,
SelectComponentProps,
SelectV2ComponentProps,
CascaderComponentProps,
SwitchComponentProps,
RateComponentProps,
ColorPickerComponentProps,
TransferComponentProps,
RadioOption,
RadioGroupComponentProps,
RadioButtonComponentProps,
CheckboxOption,
CheckboxGroupComponentProps,
DividerComponentProps,
DatePickerComponentProps,
DateTimePickerComponentProps,
TimePickerComponentProps,
TimeSelectComponentProps,
ColProps,
FormSetProps,
FormItemProps,
FormSchema,
FormProps,
PlaceholderModel,
InputPasswordComponentProps,
TreeSelectComponentProps
} from './src/types'
export interface FormExpose {
setValues: (data: Recordable) => void
setProps: (props: Recordable) => void
delSchema: (field: string) => void
addSchema: (formSchema: FormSchema, index?: number) => void
setSchema: (schemaProps: FormSetProps[]) => void
formModel: Recordable
getComponentExpose: (field: string) => any
getFormItemExpose: (field: string) => any
}
export { Form }

View File

@@ -0,0 +1,443 @@
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
import {
ElForm,
ElFormItem,
ElRow,
ElCol,
FormRules,
ComponentSize
// FormItemProp
} from 'element-plus'
import { componentMap } from './helper/componentMap'
import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper'
import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
initModel
} from './helper'
import { useRenderSelect } from './components/useRenderSelect'
import { useRenderRadio } from './components/useRenderRadio'
import { useRenderCheckbox } from './components/useRenderCheckbox'
import { useDesign } from '@/hooks/web/useDesign'
import { findIndex } from '@/utils'
import { get, set } from 'lodash-es'
import { FormProps } from './types'
import {
FormSchema,
FormSetProps,
ComponentNameEnum,
SelectComponentProps,
RadioGroupComponentProps,
CheckboxGroupComponentProps
} from './types'
const { renderSelectOptions } = useRenderSelect()
const { renderRadioOptions } = useRenderRadio()
const { renderCheckboxOptions } = useRenderCheckbox()
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('form')
export default defineComponent({
name: 'Form',
props: {
// 生成Form的布局结构数组
schema: {
type: Array as PropType<FormSchema[]>,
default: () => []
},
// 是否需要栅格布局
isCol: propTypes.bool.def(true),
// 表单数据对象
model: {
type: Object as PropType<any>,
default: () => ({})
},
// 是否自动设置placeholder
autoSetPlaceholder: propTypes.bool.def(true),
// 是否自定义内容
isCustom: propTypes.bool.def(false),
// 表单label宽度
labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
rules: {
type: Object as PropType<FormRules>,
default: () => ({})
},
labelPosition: propTypes.oneOf(['left', 'right', 'top']).def('right'),
labelSuffix: propTypes.string.def(''),
hideRequiredAsterisk: propTypes.bool.def(false),
requireAsteriskPosition: propTypes.oneOf(['left', 'right']).def('left'),
showMessage: propTypes.bool.def(true),
inlineMessage: propTypes.bool.def(false),
statusIcon: propTypes.bool.def(false),
validateOnRuleChange: propTypes.bool.def(true),
size: {
type: String as PropType<ComponentSize>,
default: undefined
},
disabled: propTypes.bool.def(false),
scrollToError: propTypes.bool.def(false),
scrollToErrorOffset: propTypes.oneOfType([Boolean, Object]).def(undefined)
// onValidate: {
// type: Function as PropType<(prop: FormItemProp, isValid: boolean, message: string) => void>,
// default: () => {}
// }
},
emits: ['register'],
setup(props, { slots, expose, emit }) {
// element form 实例
const elFormRef = ref<ComponentRef<typeof ElForm>>()
const mergeProps = ref<FormProps>({})
const getProps = computed(() => {
const propsObj = { ...props }
Object.assign(propsObj, unref(mergeProps))
return propsObj
})
// 存储表单实例
const formComponents = ref({})
// 存储form-item实例
const formItemComponents = ref({})
// 表单数据
const formModel = ref<Recordable>(props.model)
onMounted(() => {
emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
})
// 对表单赋值
const setValues = (data: Recordable = {}) => {
formModel.value = Object.assign(unref(formModel), data)
}
const setProps = (props: FormProps = {}) => {
mergeProps.value = Object.assign(unref(mergeProps), props)
}
const delSchema = (field: string) => {
const { schema } = unref(getProps)
const index = findIndex(schema, (v: FormSchema) => v.field === field)
if (index > -1) {
schema.splice(index, 1)
}
}
const addSchema = (formSchema: FormSchema, index?: number) => {
const { schema } = unref(getProps)
if (index !== void 0) {
schema.splice(index, 0, formSchema)
return
}
schema.push(formSchema)
}
const setSchema = (schemaProps: FormSetProps[]) => {
const { schema } = unref(getProps)
for (const v of schema) {
for (const item of schemaProps) {
if (v.field === item.field) {
set(v, item.path, item.value)
}
}
}
}
const getOptions = async (fn: Function, item: FormSchema) => {
const options = await fn()
if (!options || options.length == 0) return
setSchema([
{
field: item.field,
path:
item.component === ComponentNameEnum.TREE_SELECT ||
item.component === ComponentNameEnum.TRANSFER
? 'componentProps.data'
: 'componentProps.options',
value: options
}
])
}
/**
* @description: 获取表单组件实例
* @param filed 表单字段
*/
const getComponentExpose = (filed: string) => {
return unref(formComponents)[filed]
}
/**
* @description: 获取formItem实例
* @param filed 表单字段
*/
const getFormItemExpose = (filed: string) => {
return unref(formItemComponents)[filed]
}
const setComponentRefMap = (ref: any, filed: string) => {
formComponents.value[filed] = ref
}
const setFormItemRefMap = (ref: any, filed: string) => {
formItemComponents.value[filed] = ref
}
expose({
setValues,
formModel,
setProps,
delSchema,
addSchema,
setSchema,
getComponentExpose,
getFormItemExpose
})
// 监听表单结构化数组重新生成formModel
watch(
() => unref(getProps).schema,
(schema = []) => {
formModel.value = initModel(schema, unref(formModel))
},
{
immediate: true,
deep: true
}
)
// 渲染包裹标签,是否使用栅格布局
const renderWrap = () => {
const { isCol } = unref(getProps)
const content = isCol ? (
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
) : (
renderFormItemWrap()
)
return content
}
// 是否要渲染el-col
const renderFormItemWrap = () => {
// hidden属性表示隐藏不做渲染
const { schema = [], isCol } = unref(getProps)
return schema
.filter((v) => !v.remove)
.map((item) => {
// 如果是 Divider 组件,需要自己占用一行
const isDivider = item.component === 'Divider'
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
return isDivider ? (
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
) : isCol ? (
// 如果需要栅格,需要包裹 ElCol
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
) : (
renderFormItem(item)
)
})
}
// 渲染formItem
const renderFormItem = (item: FormSchema) => {
// 如果有optionApi优先使用optionApi, 并且options不存在或者为空数组
if (
item.optionApi &&
(!item.componentProps?.options || !item.componentProps?.options.length)
) {
// 内部自动调用接口,不影响其它渲染
getOptions(item.optionApi, item)
}
const formItemSlots: Recordable = {
default: () => {
if (item?.formItemProps?.slots?.default) {
return item?.formItemProps?.slots?.default(formModel.value)
} else {
const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
const { autoSetPlaceholder } = unref(getProps)
const componentSlots = (item?.componentProps as any)?.slots || {}
const slotsMap: Recordable = {
...setItemComponentSlots(componentSlots)
}
// // 如果是select组件并且没有自定义模板自动渲染options
if (item.component === ComponentNameEnum.SELECT) {
slotsMap.default = !componentSlots.default
? () => renderSelectOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as SelectComponentProps)?.options)
)
}
}
// 虚拟列表
if (item.component === ComponentNameEnum.SELECT_V2 && componentSlots.default) {
slotsMap.default = ({ item }) => {
return componentSlots.default(item)
}
}
// 单选框组和按钮样式
if (
item.component === ComponentNameEnum.RADIO_GROUP ||
item.component === ComponentNameEnum.RADIO_BUTTON
) {
slotsMap.default = !componentSlots.default
? () => renderRadioOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as CheckboxGroupComponentProps)?.options)
)
}
}
// 多选框组和按钮样式
if (
item.component === ComponentNameEnum.CHECKBOX_GROUP ||
item.component === ComponentNameEnum.CHECKBOX_BUTTON
) {
slotsMap.default = !componentSlots.default
? () => renderCheckboxOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as RadioGroupComponentProps)?.options)
)
}
}
const Comp = () => {
// 如果field是多层路径需要转换成对象
const itemVal = computed({
get: () => {
return get(formModel.value, item.field)
},
set: (val) => {
set(formModel.value, item.field, val)
}
})
return item.component === ComponentNameEnum.UPLOAD ? (
<Com
vModel:file-list={itemVal.value}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={
item.componentProps?.style || {
width: '100%'
}
}
>
{{ ...slotsMap }}
</Com>
) : (
<Com
vModel={itemVal.value}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={
item.componentProps?.style || {
width: '100%'
}
}
>
{{ ...slotsMap }}
</Com>
)
}
return <>{Comp()}</>
}
}
}
if (item?.formItemProps?.slots?.label) {
formItemSlots.label = (...args: any[]) => {
return (item?.formItemProps?.slots as any)?.label(...args)
}
}
if (item?.formItemProps?.slots?.error) {
formItemSlots.error = (...args: any[]) => {
return (item?.formItemProps?.slots as any)?.error(...args)
}
}
return (
<ElFormItem
v-show={!item.hidden}
ref={(el: any) => setFormItemRefMap(el, item.field)}
{...(item.formItemProps || {})}
prop={item.field}
label={item.label || ''}
>
{formItemSlots}
</ElFormItem>
)
}
// 过滤传入Form组件的属性
const getFormBindValue = () => {
// 避免在标签上出现多余的属性
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
const props = { ...unref(getProps) }
for (const key in props) {
if (delKeys.indexOf(key) !== -1) {
delete props[key]
}
}
return props as FormProps
}
return () => (
<ElForm
ref={elFormRef}
{...getFormBindValue()}
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
class={prefixCls}
// @ts-ignore
onSubmit={(e: Event) => {
e.preventDefault()
}}
>
{{
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
default: () => {
const { isCustom } = unref(getProps)
return isCustom ? getSlot(slots, 'default') : renderWrap()
}
}}
</ElForm>
)
}
})
</script>
<style lang="less" scoped>
.@{elNamespace}-form.@{adminNamespace}-form .@{elNamespace}-row {
margin-right: 0 !important;
margin-left: 0 !important;
}
.@{elNamespace}-form--inline {
:deep(.el-form-item__content) {
& > :first-child {
min-width: 229.5px;
}
}
.@{elNamespace}-input-number {
// 229.5px是兼容el-input-number的最小宽度,
min-width: 229.5px;
}
}
</style>

View File

@@ -0,0 +1,31 @@
import { FormSchema, ComponentNameEnum, CheckboxGroupComponentProps } from '../types'
import { ElCheckbox, ElCheckboxButton } from 'element-plus'
import { defineComponent } from 'vue'
export const useRenderCheckbox = () => {
const renderCheckboxOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const componentProps = item?.componentProps as CheckboxGroupComponentProps
const valueAlias = componentProps?.props?.value || 'value'
const labelAlias = componentProps?.props?.label || 'label'
const disabledAlias = componentProps?.props?.disabled || 'disabled'
const Com = (
item.component === ComponentNameEnum.CHECKBOX_GROUP ? ElCheckbox : ElCheckboxButton
) as ReturnType<typeof defineComponent>
return componentProps?.options?.map((option) => {
const { ...other } = option
return (
<Com
{...other}
disabled={option[disabledAlias || 'disabled']}
label={option[labelAlias || 'label']}
value={option[valueAlias || 'value']}
></Com>
)
})
}
return {
renderCheckboxOptions
}
}

View File

@@ -0,0 +1,31 @@
import { FormSchema, ComponentNameEnum, RadioGroupComponentProps } from '../types'
import { ElRadio, ElRadioButton } from 'element-plus'
import { defineComponent } from 'vue'
export const useRenderRadio = () => {
const renderRadioOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const componentProps = item?.componentProps as RadioGroupComponentProps
const valueAlias = componentProps?.props?.value || 'value'
const labelAlias = componentProps?.props?.label || 'label'
const disabledAlias = componentProps?.props?.disabled || 'disabled'
const Com = (
item.component === ComponentNameEnum.RADIO_GROUP ? ElRadio : ElRadioButton
) as ReturnType<typeof defineComponent>
return componentProps?.options?.map((option) => {
const { ...other } = option
return (
<Com
{...other}
disabled={option[disabledAlias || 'disabled']}
label={option[labelAlias || 'label']}
value={option[valueAlias || 'value']}
></Com>
)
})
}
return {
renderRadioOptions
}
}

View File

@@ -0,0 +1,58 @@
import { ElOption, ElOptionGroup } from 'element-plus'
import { FormSchema, SelectComponentProps, SelectOption } from '../types'
export const useRenderSelect = () => {
// 渲染 select options
const renderSelectOptions = (item: FormSchema) => {
const componentsProps = item?.componentProps as SelectComponentProps
const optionGroupDefaultSlot = componentsProps?.slots?.optionGroupDefault
// 如果有别名,就取别名
const labelAlias = componentsProps?.props?.label
const keyAlias = componentsProps?.props?.key
return componentsProps?.options?.map((option) => {
if (option?.options?.length) {
return optionGroupDefaultSlot ? (
optionGroupDefaultSlot(option)
) : (
<ElOptionGroup label={option[labelAlias || 'label']} key={option[keyAlias || 'key']}>
{{
default: () =>
option?.options?.map((v) => {
return renderSelectOptionItem(item, v)
})
}}
</ElOptionGroup>
)
} else {
return renderSelectOptionItem(item, option)
}
})
}
// 渲染 select option item
const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
// 如果有别名,就取别名
const componentsProps = item.componentProps as SelectComponentProps
const labelAlias = componentsProps?.props?.label
const valueAlias = componentsProps?.props?.value
const keyAlias = componentsProps?.props?.key
const optionDefaultSlot = componentsProps.slots?.optionDefault
return (
<ElOption
{...option}
key={option[keyAlias || 'key']}
label={option[labelAlias || 'label']}
value={option[valueAlias || 'value']}
>
{{
default: () => (optionDefaultSlot ? optionDefaultSlot(option) : undefined)
}}
</ElOption>
)
}
return {
renderSelectOptions
}
}

View File

@@ -0,0 +1,59 @@
import type { Component } from 'vue'
import {
ElCascader,
ElCheckboxGroup,
ElColorPicker,
ElDatePicker,
ElInput,
ElInputNumber,
ElRadioGroup,
ElRate,
ElSelect,
ElSelectV2,
ElSlider,
ElSwitch,
ElTimePicker,
ElTimeSelect,
ElTransfer,
ElAutocomplete,
ElDivider,
ElTreeSelect,
ElUpload
} from 'element-plus'
import { InputPassword } from '@/components/InputPassword'
import { Editor } from '@/components/Editor'
import { JsonEditor } from '@/components/JsonEditor'
import { IconPicker } from '@/components/IconPicker'
import { IAgree } from '@/components/IAgree'
import { ComponentName } from '../types'
const componentMap: Recordable<Component, ComponentName> = {
RadioGroup: ElRadioGroup,
RadioButton: ElRadioGroup,
CheckboxGroup: ElCheckboxGroup,
CheckboxButton: ElCheckboxGroup,
Input: ElInput,
Autocomplete: ElAutocomplete,
InputNumber: ElInputNumber,
Select: ElSelect,
Cascader: ElCascader,
Switch: ElSwitch,
Slider: ElSlider,
TimePicker: ElTimePicker,
DatePicker: ElDatePicker,
Rate: ElRate,
ColorPicker: ElColorPicker,
Transfer: ElTransfer,
Divider: ElDivider,
TimeSelect: ElTimeSelect,
SelectV2: ElSelectV2,
InputPassword: InputPassword,
Editor: Editor,
TreeSelect: ElTreeSelect,
Upload: ElUpload,
JsonEditor: JsonEditor,
IconPicker: IconPicker,
IAgree: IAgree
}
export { componentMap }

View File

@@ -0,0 +1,169 @@
import { useI18n } from '@/hooks/web/useI18n'
import { PlaceholderModel, FormSchema, ComponentNameEnum, ColProps } from '../types'
import { isFunction } from '@/utils/is'
import { firstUpperCase, humpToDash } from '@/utils'
import { set, get } from 'lodash-es'
const { t } = useI18n()
/**
*
* @param schema 对应组件数据
* @returns 返回提示信息对象
* @description 用于自动设置placeholder
*/
export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
const textMap = [
ComponentNameEnum.INPUT,
ComponentNameEnum.AUTOCOMPLETE,
ComponentNameEnum.INPUT_NUMBER,
ComponentNameEnum.INPUT_PASSWORD
]
const selectMap = [
ComponentNameEnum.SELECT,
ComponentNameEnum.TIME_PICKER,
ComponentNameEnum.DATE_PICKER,
ComponentNameEnum.TIME_SELECT,
ComponentNameEnum.SELECT_V2
]
if (textMap.includes(schema?.component as ComponentNameEnum)) {
return {
placeholder: t('common.inputText')
}
}
if (selectMap.includes(schema?.component as ComponentNameEnum)) {
// 一些范围选择器
const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']
if (
twoTextMap.includes(
((schema?.componentProps as any)?.type ||
(schema?.componentProps as any)?.isRange) as string
)
) {
return {
startPlaceholder: t('common.startTimeText'),
endPlaceholder: t('common.endTimeText'),
rangeSeparator: '-'
}
} else {
return {
placeholder: t('common.selectText')
}
}
}
return {}
}
/**
*
* @param col 内置栅格
* @returns 返回栅格属性
* @description 合并传入进来的栅格属性
*/
export const setGridProp = (col: ColProps = {}): ColProps => {
const colProps: ColProps = {
// 如果有span代表用户优先级更高所以不需要默认栅格
...(col.span
? {}
: {
xs: 24,
sm: 12,
md: 12,
lg: 12,
xl: 12
}),
...col
}
return colProps
}
/**
*
* @param item 传入的组件属性
* @returns 默认添加 clearable 属性
*/
export const setComponentProps = (item: FormSchema): Recordable => {
// const notNeedClearable = ['ColorPicker']
// 拆分事件并组合
const onEvents = (item?.componentProps as any)?.on || {}
const newOnEvents: Recordable = {}
for (const key in onEvents) {
if (onEvents[key]) {
newOnEvents[`on${firstUpperCase(key)}`] = (...args: any[]) => {
onEvents[key](...args)
}
}
}
const componentProps: Recordable = {
clearable: true,
...item.componentProps,
...newOnEvents
}
// 需要删除额外的属性
if (componentProps.slots) {
delete componentProps.slots
}
if (componentProps.on) {
delete componentProps.on
}
return componentProps
}
/**
*
* @param formModel 表单数据
* @param slotsProps 插槽属性
*/
export const setItemComponentSlots = (slotsProps: Recordable = {}): Recordable => {
const slotObj: Recordable = {}
for (const key in slotsProps) {
if (slotsProps[key]) {
if (isFunction(slotsProps[key])) {
slotObj[humpToDash(key)] = (...args: any[]) => {
return slotsProps[key]?.(...args)
}
} else {
slotObj[humpToDash(key)] = () => {
return slotsProps[key]
}
}
}
}
return slotObj
}
/**
*
* @param schema Form表单结构化数组
* @param formModel FormMoel
* @returns FormMoel
* @description 生成对应的formModel
*/
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
const model: Recordable = { ...formModel }
schema.map((v) => {
if (v.remove) {
delete model[v.field]
} else if (v.component !== 'Divider') {
// const hasField = Reflect.has(model, v.field)
const hasField = get(model, v.field)
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
set(
model,
v.field,
hasField !== void 0 ? get(model, v.field) : v.value !== void 0 ? v.value : undefined
)
// model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
}
})
// 如果 schema 对应的 field 不存在,则删除 model 中的对应的 field
for (let i = 0; i < schema.length; i++) {
const key = schema[i].field
if (!Object.prototype.hasOwnProperty.call(model, key)) {
delete model[key]
}
}
return model
}

View File

@@ -0,0 +1,670 @@
import {
AutocompleteProps,
InputNumberProps,
CascaderProps,
CascaderNode,
CascaderValue,
SwitchProps,
ComponentSize,
InputProps,
RateProps,
ColorPickerProps,
TransferProps,
RadioGroupProps,
RadioButtonProps,
CheckboxGroupProps,
DividerProps,
DatePickerProps,
FormItemProps as ElFormItemProps,
FormProps as ElFormProps,
ISelectProps,
UploadProps
} from 'element-plus'
import { IEditorConfig } from '@wangeditor/editor'
import { JsonEditorProps } from '@/components/JsonEditor'
import { IAgreeProps } from '@/components/IAgree'
import { CSSProperties } from 'vue'
export interface PlaceholderModel {
placeholder?: string
startPlaceholder?: string
endPlaceholder?: string
rangeSeparator?: string
}
export enum ComponentNameEnum {
RADIO_GROUP = 'RadioGroup',
RADIO_BUTTON = 'RadioButton',
CHECKBOX_GROUP = 'CheckboxGroup',
CHECKBOX_BUTTON = 'CheckboxButton',
INPUT = 'Input',
AUTOCOMPLETE = 'Autocomplete',
INPUT_NUMBER = 'InputNumber',
SELECT = 'Select',
CASCADER = 'Cascader',
SWITCH = 'Switch',
SLIDER = 'Slider',
TIME_PICKER = 'TimePicker',
DATE_PICKER = 'DatePicker',
RATE = 'Rate',
COLOR_PICKER = 'ColorPicker',
TRANSFER = 'Transfer',
DIVIDER = 'Divider',
TIME_SELECT = 'TimeSelect',
SELECT_V2 = 'SelectV2',
INPUT_PASSWORD = 'InputPassword',
EDITOR = 'Editor',
TREE_SELECT = 'TreeSelect',
UPLOAD = 'Upload',
JSON_EDITOR = 'JsonEditor',
ICON_PICKER = 'IconPicker',
I_AGREE = 'IAgree'
}
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
? K extends string
? K extends `${infer A}_${infer B}`
? `${Capitalize<Lowercase<A>>}${Capitalize<Lowercase<B>>}`
: Capitalize<Lowercase<K>>
: never
: never
export type ComponentName = CamelCaseComponentName
export interface InputPasswordComponentProps {
strength?: boolean
style?: CSSProperties
}
export interface InputComponentProps extends Partial<InputProps> {
rows?: number
on?: {
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
change?: (value: string | number) => void
clear?: () => void
input?: (value: string | number) => void
}
slots?: {
prefix?: (...args: any[]) => JSX.Element | null
suffix?: (...args: any[]) => JSX.Element | null
prepend?: (...args: any[]) => JSX.Element | null
append?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface AutocompleteComponentProps extends Partial<AutocompleteProps> {
on?: {
select?: (item: any) => void
change?: (value: string | number) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
prefix?: (...args: any[]) => JSX.Element | null
suffix?: (...args: any[]) => JSX.Element | null
prepend?: (...args: any[]) => JSX.Element | null
append?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface InputNumberComponentProps extends Partial<InputNumberProps> {
on?: {
change?: (currentValue: number | undefined, oldValue: number | undefined) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
style?: CSSProperties
}
export interface SelectOption {
label?: string
disabled?: boolean
value?: any
key?: string | number
options?: SelectOption[]
[key: string]: any
}
export interface SelectComponentProps extends Omit<Partial<ISelectProps>, 'options'> {
/**
* 数据源的字段别名
*/
props?: {
key?: string
value?: string
label?: string
children?: string
}
on?: {
change?: (value: string | number | boolean | object) => void
visibleChange?: (visible: boolean) => void
removeTag?: (tag: any) => void
clear?: () => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
slots?: {
default?: (options: SelectOption[]) => JSX.Element[] | null
optionGroupDefault?: (item: SelectOption) => JSX.Element
optionDefault?: (option: SelectOption) => JSX.Element | null
prefix?: (...args: any[]) => JSX.Element | null
empty?: (...args: any[]) => JSX.Element | null
}
options?: SelectOption[]
style?: CSSProperties
}
export interface SelectV2ComponentProps {
multiple?: boolean
disabled?: boolean
valueKey?: string
size?: ComponentSize
clearable?: boolean
clearIcon?: string | JSX.Element | null
collapseTags?: boolean
multipleLimit?: number
name?: string
effect?: string
autocomplete?: string
placeholder?: string
filterable?: boolean
allowCreate?: boolean
reserveKeyword?: boolean
noDataText?: string
popperClass?: string
teleported?: boolean
persistent?: boolean
popperOptions?: any
automaticDropdown?: boolean
height?: number
scrollbarAlwaysOn?: boolean
remote?: boolean
remoteMethod?: (query: string) => void
validateEvent?: boolean
placement?: AutocompleteProps['placement']
collapseTagsTooltip?: boolean
on?: {
change?: (value: string | number | boolean | object) => void
visibleChange?: (visible: boolean) => void
removeTag?: (tag: any) => void
clear?: () => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
options?: SelectOption[]
slots?: {
default?: (option: SelectOption) => JSX.Element | null
}
style?: CSSProperties
}
export interface CascaderComponentProps {
options?: Record<string, unknown>[]
props?: CascaderProps
size?: ComponentSize
placeholder?: string
disabled?: boolean
clearable?: boolean
showAllLevels?: boolean
collapseTags?: boolean
collapseTagsTooltip?: boolean
separator?: string
filterable?: boolean
filterMethod?: (node: CascaderNode, keyword: string) => boolean
debounce?: number
beforeFilter?: (value: string) => boolean
popperClass?: string
teleported?: boolean
tagType?: ElementPlusInfoType
validateEvent?: boolean
on?: {
change?: (value: CascaderValue) => void
expandChange?: (value: CascaderValue) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
visibleChange?: (value: boolean) => void
removeTag?: (value: CascaderNode['valueByOption']) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
empty?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface SwitchComponentProps extends Partial<SwitchProps> {
on?: {
change?: (value: boolean | string | number) => void
}
style?: CSSProperties
}
export interface RateComponentProps extends Partial<RateProps> {
on?: {
change?: (value: number) => void
}
style?: CSSProperties
}
export interface ColorPickerComponentProps extends Partial<ColorPickerProps> {
on?: {
change?: (value: string) => void
activeChange?: (value: string) => void
}
style?: CSSProperties
}
export interface TransferComponentProps extends Partial<TransferProps> {
on?: {
change?: (
value: number | string,
direction: 'left' | 'right',
movedKeys: string[] | number[]
) => void
leftCheckChange?: (value: any[]) => void
rightCheckChange?: (value: any[]) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
leftFooter?: (...args: any[]) => JSX.Element | null
rightFooter?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface RadioOption {
label?: string
value?: string | number | boolean
disabled?: boolean
border?: boolean
size?: ComponentSize
name?: string
[key: string]: any
}
export interface RadioGroupComponentProps extends Partial<RadioGroupProps> {
options?: RadioOption[]
/**
* 数据源的字段别名
*/
props?: {
label?: string
value?: string
disabled?: string
}
on?: {
change?: (value: string | number | boolean) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element[] | null
}
style?: CSSProperties
}
export interface RadioButtonComponentProps extends Partial<RadioButtonProps> {
options?: RadioOption[]
/**
* 数据源的字段别名
*/
props?: {
label?: string
value?: string
disabled?: string
}
on?: {
change?: (value: string | number | boolean) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element[] | null
}
style?: CSSProperties
}
export interface CheckboxOption {
label?: string
value?: string | number | boolean
disabled?: boolean
trueLabel?: string | number
falseLabel?: string | number
border?: boolean
size?: ComponentSize
name?: string
checked?: boolean
indeterminate?: boolean
validateEvent?: boolean
tabindex?: number | string
id?: string
controls?: boolean
[key: string]: any
}
export interface CheckboxGroupComponentProps extends Partial<CheckboxGroupProps> {
options?: CheckboxOption[]
/**
* 数据源的字段别名
*/
props?: {
label?: string
value?: string
disabled?: string
}
on?: {
change?: (value: string | number | boolean) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element[] | null
}
style?: CSSProperties
}
export interface DividerComponentProps extends Partial<DividerProps> {
on?: {
change?: (value: number) => void
input?: (value: number) => void
}
style?: CSSProperties
}
export interface DatePickerComponentProps extends Partial<DatePickerProps> {
on?: {
change?: (value: string | Date | number | string[]) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
calendarChange?: (val: [Date, Date]) => void
panelChange?: (date, mode, view) => void
visibleChange?: (visibility: boolean) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
rangeSeparator?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface DateTimePickerComponentProps {
readonly?: boolean
disabled?: boolean
editable?: boolean
clearable?: boolean
size?: ComponentSize
placeholder?: string
startPlaceholder?: string
endPlaceholder?: string
timeArrowControl?: boolean
type?: 'year' | 'month' | 'date' | 'datetime' | 'datetimerange' | 'daterange' | 'week'
format?: string
popperClass?: string
rangeSeparator?: string
defaultValue?: Date | [Date, Date]
defaultTime?: Date | [Date, Date]
valueFormat?: string
id?: string
name?: string
unlinkPanels?: boolean
prefixIcon?: string | JSX.Element
clearIcon?: string | JSX.Element
shortcuts?: Array<{ text: string; value: Date | Function }>
disabledDate?: (date: Date) => boolean
cellClassName?: string | ((date: Date) => string | undefined)
teleported?: boolean
on?: {
change?: (value: string | Date | number | string[]) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
calendarChange?: (val: [Date, Date]) => void
visibleChange?: (visibility: boolean) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
rangeSeparator?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface TimePickerComponentProps {
readonly?: boolean
disabled?: boolean
editable?: boolean
clearable?: boolean
size?: ComponentSize
placeholder?: string
startPlaceholder?: string
endPlaceholder?: string
isRange?: boolean
arrowControl?: boolean
popperClass?: string
rangeSeparator?: string
format?: string
defaultValue?: Date | [Date, Date]
id?: string
name?: string
label?: string
prefixIcon?: string | JSX.Element
clearIcon?: string | JSX.Element
disabledHours?: (role: string, comparingDate?: any) => number[]
disabledMinutes?: (hour: number, role: string, comparingDate?: any) => number[]
disabledSeconds?: (hour: number, minute: number, role: string, comparingDate?: any) => number[]
teleported?: boolean
tabindex?: number | string
on?: {
change: (
val: number | string | Date | [number, number] | [string, string] | [Date, Date]
) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
visibleChange?: (visibility: boolean) => void
}
style?: CSSProperties
}
export interface TimeSelectComponentProps {
disabled?: boolean
editable?: boolean
clearable?: boolean
size?: ComponentSize
placeholder?: string
name?: string
effect?: string
prefixIcon?: string | JSX.Element
clearIcon?: string | JSX.Element
start?: string
end?: string
step?: string
minTime?: string
maxTime?: string
format?: string
on?: {
change?: (val: string) => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
style?: CSSProperties
}
export interface EditorComponentProps {
editorConfig?: IEditorConfig
style?: CSSProperties
}
export interface ColProps {
span?: number
xs?: number
sm?: number
md?: number
lg?: number
xl?: number
tag?: string
}
export interface FormSetProps {
field: string
path: string
value: any
}
export interface FormItemProps extends Partial<ElFormItemProps> {
style?: CSSProperties
slots?: {
default?: (...args: any[]) => JSX.Element | null
label?: (...args: any[]) => JSX.Element | null
error?: (...args: any[]) => JSX.Element | null
}
}
export interface UploadComponentProps extends Partial<UploadProps> {
slots?: {
default?: (...args: any[]) => JSX.Element | null
trigger?: (...args: any[]) => JSX.Element | null
tip?: (...args: any[]) => JSX.Element | null
file?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface TreeSelectComponentProps
extends Omit<Partial<SelectComponentProps>, 'props' | 'on' | 'slots'> {
data?: any[]
emptyText?: string
nodeKey?: string
props?: {
children?: string
label?: string | ((...args: any[]) => string)
disabled?: string | ((...args: any[]) => string)
isLeaf?: string | ((...args: any[]) => string)
class?: string | ((...args: any[]) => string)
}
renderAfterExpand?: boolean
load?: (...args: any[]) => Promise<any>
renderContent?: (...args: any[]) => JSX.Element | null
highlightCurrent?: boolean
defaultExpandAll?: boolean
expandOnClickNode?: boolean
checkOnClickNode?: boolean
autoExpandParent?: boolean
defaultExpandedKeys?: any[]
showCheckbox?: boolean
checkStrictly?: boolean
defaultCheckedKeys?: any[]
currentNodeKey?: string | number
filterNodeMethod?: (...args: any[]) => boolean
accordion?: boolean
indent?: number
icon?: string | ((...args: any[]) => JSX.Element | null)
lazy?: boolean
draggable?: boolean
allowDrag?: (...args: any[]) => boolean
allowDrop?: (...args: any[]) => boolean
on?: {
change?: (value: string | number | boolean | object) => void
visibleChange?: (visible: boolean) => void
removeTag?: (tag: any) => void
clear?: () => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
nodeClick?: (...args: any[]) => void
nodeContextMenu?: (...args: any[]) => void
checkChange?: (...args: any[]) => void
check?: (...args: any[]) => void
currentChange?: (...args: any[]) => void
nodeExpand?: (...args: any[]) => void
nodeCollapse?: (...args: any[]) => void
nodeDragStart?: (...args: any[]) => void
nodeDragEnter?: (...args: any[]) => void
nodeDragLeave?: (...args: any[]) => void
nodeDragOver?: (...args: any[]) => void
nodeDragEnd?: (...args: any[]) => void
nodeDrop?: (...args: any[]) => void
}
slots?: {
default?: (...args: any[]) => JSX.Element | null
optionGroupDefault?: (item: SelectOption) => JSX.Element
optionDefault?: (option: SelectOption) => JSX.Element | null
prefix?: (...args: any[]) => JSX.Element | null
empty?: (...args: any[]) => JSX.Element | null
}
style?: CSSProperties
}
export interface FormSchema {
/**
* 唯一标识
*/
field: string
/**
* 标题
*/
label?: string
/**
* col组件属性
*/
colProps?: ColProps
/**
* 表单组件属性具体可以查看element-plus文档
*/
componentProps?:
| InputComponentProps
| AutocompleteComponentProps
| InputNumberComponentProps
| SelectComponentProps
| SelectV2ComponentProps
| CascaderComponentProps
| SwitchComponentProps
| RateComponentProps
| ColorPickerComponentProps
| TransferComponentProps
| RadioGroupComponentProps
| RadioButtonComponentProps
| DividerComponentProps
| DatePickerComponentProps
| DateTimePickerComponentProps
| TimePickerComponentProps
| InputPasswordComponentProps
| TreeSelectComponentProps
| UploadComponentProps
| JsonEditorProps
| IAgreeProps
| any
/**
* formItem组件属性具体可以查看element-plus文档
*/
formItemProps?: FormItemProps
/**
* 渲染的组件名称
*/
component?: ComponentName
/**
* 初始值
*/
value?: any
/**
* 是否隐藏如果为true会连同值一同删除类似v-if
*/
remove?: boolean
/**
* 样式隐藏不会把值一同删掉类似v-show
*/
hidden?: boolean
/**
* @returns 远程加载下拉项
*/
optionApi?: any
}
export interface FormProps extends Partial<ElFormProps> {
schema?: FormSchema[]
isCol?: boolean
model?: Recordable
autoSetPlaceholder?: boolean
isCustom?: boolean
[key: string]: any
}

View File

@@ -0,0 +1,3 @@
import Highlight from './src/Highlight.vue'
export { Highlight }

View File

@@ -0,0 +1,65 @@
<script lang="tsx">
import { defineComponent, PropType, computed, h, unref } from 'vue'
import { propTypes } from '@/utils/propTypes'
export default defineComponent({
name: 'Highlight',
props: {
tag: propTypes.string.def('span'),
keys: {
type: Array as PropType<string[]>,
default: () => []
},
color: propTypes.string.def('var(--el-color-primary)')
},
emits: ['click'],
setup(props, { emit, slots }) {
const keyNodes = computed(() => {
return props.keys.map((key) => {
return h(
'span',
{
onClick: () => {
emit('click', key)
},
style: {
color: props.color,
cursor: 'pointer'
}
},
key
)
})
})
const parseText = (text: string) => {
props.keys.forEach((key, index) => {
const regexp = new RegExp(key, 'g')
text = text.replace(regexp, `{{${index}}}`)
})
return text.split(/{{|}}/)
}
const renderText = () => {
if (!slots?.default) return null
const node = slots?.default()[0].children
if (!node) {
return slots?.default()[0]
}
const textArray = parseText(node as string)
const regexp = /^[0-9]*$/
const nodes = textArray.map((t) => {
if (regexp.test(t)) {
return unref(keyNodes)[t] || t
}
return t
})
return h(props.tag, nodes)
}
return () => renderText()
}
})
</script>

View File

@@ -0,0 +1,4 @@
import IAgree from './src/IAgree.vue'
export type { LinkItem, IAgreeProps } from './src/types'
export { IAgree }

View File

@@ -0,0 +1,41 @@
<script lang="ts" setup>
import { ElCheckbox } from 'element-plus'
import { Highlight } from '@/components/Highlight'
import { PropType, computed } from 'vue'
import { LinkItem } from './types'
const props = defineProps({
text: {
type: String,
default: ''
},
link: {
type: Array as PropType<LinkItem[]>,
default: undefined
}
})
const modelValue = defineModel<boolean>()
const highlightKeys = computed(() => {
return props.link?.map((item) => item.text) || []
})
const keyClick = (key: string) => {
const linkItem = props.link?.find((item) => item.text === key)
if (linkItem?.url) {
window.open(linkItem.url)
return
}
if (linkItem?.onClick) {
linkItem.onClick()
}
}
</script>
<template>
<div class="flex items-center">
<ElCheckbox v-model="modelValue" class="mr-0px!" />
<Highlight class="ml-10px" :keys="highlightKeys" @click="keyClick">{{ text }}</Highlight>
</div>
</template>

View File

@@ -0,0 +1,10 @@
export interface LinkItem {
text: string
url?: string
onClick?: () => void
}
export interface IAgreeProps {
text: string
link: LinkItem[]
}

View File

@@ -0,0 +1,5 @@
import Icon from './src/Icon.vue'
export type { IconTypes } from './src/types'
export { Icon }

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import { computed, unref } from 'vue'
import { ElIcon } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { Icon } from '@iconify/vue'
import { ICON_PREFIX } from '@/constants'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('icon')
const props = defineProps({
// icon name
icon: propTypes.string,
// icon color
color: propTypes.string,
// icon size
size: propTypes.number.def(16),
hoverColor: propTypes.string
})
const isLocal = computed(() => props.icon.startsWith('svg-icon:'))
const symbolId = computed(() => {
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
})
// 是否使用在线图标
const isUseOnline = computed(() => {
return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
})
const getIconifyStyle = computed(() => {
const { color, size } = props
return {
fontSize: `${size}px`,
color
}
})
const getIconName = computed(() => {
return props.icon.startsWith(ICON_PREFIX) ? props.icon.replace(ICON_PREFIX, '') : props.icon
})
</script>
<template>
<ElIcon :class="prefixCls" :size="size" :color="color">
<svg v-if="isLocal" aria-hidden="true">
<use :xlink:href="symbolId" />
</svg>
<template v-else>
<Icon v-if="isUseOnline" :icon="getIconName" :style="getIconifyStyle" />
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
</template>
</ElIcon>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{adminNamespace}-icon';
.@{prefix-cls},
.iconify {
:deep(svg) {
&:hover {
// stylelint-disable-next-line
color: v-bind(hoverColor) !important;
}
}
}
.iconify {
&:hover {
// stylelint-disable-next-line
color: v-bind(hoverColor) !important;
}
}
</style>

View File

@@ -0,0 +1,6 @@
export interface IconTypes {
size?: number
color?: string
icon: string
hoverColor?: string
}

View File

@@ -0,0 +1,3 @@
import IconPicker from './src/IconPicker.vue'
export { IconPicker }

View File

@@ -0,0 +1,194 @@
<script setup lang="ts">
import epIcons from './data/icons.ep'
import antIcons from './data/icons.ant-design'
import tIcons from './data/icons.tdesign'
import { useDesign } from '@/hooks/web/useDesign'
import { ElInput, ElPopover, ElScrollbar, ElTabs, ElTabPane, ElPagination } from 'element-plus'
import { useAppStore } from '@/store/modules/app'
import { computed, CSSProperties, ref, unref, watch } from 'vue'
import { nextTick } from 'vue'
const init = async (icon?: string) => {
if (!icon) return
const iconInfo = icon.split(':')
iconName.value = iconInfo[0]
const wrapIndex = icons.findIndex((item) => item.prefix === iconInfo[0])
// 查询当前icon的索引
const index = filterItemIcons(icons[wrapIndex].icons).findIndex((item) => item === icon)
// 计算当前icon的页码
await nextTick()
currentPage.value = Math.ceil((index + 1) / unref(pageSize))
}
const modelValue = defineModel<string>()
const appStore = useAppStore()
const size = computed(() => appStore.getCurrentSize)
const iconSize = computed(() => {
return unref(size) === 'small'
? 'var(--el-component-size-small)'
: unref(size) === 'large'
? 'var(--el-component-size-large)'
: 'var(--el-component-size)'
})
const iconWrapStyle = computed((): CSSProperties => {
return {
width: unref(iconSize),
height: unref(iconSize),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset',
position: 'relative',
left: '-1px',
cursor: 'pointer'
}
})
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('icon-picker')
const icons = [epIcons, antIcons, tIcons]
const iconName = ref(icons[0].prefix)
const currentIconNameIndex = computed(() => {
const index = icons.findIndex((item) => item.prefix === unref(iconName))
return index < 0 ? 0 : index
})
const tabChange = () => {
currentPage.value = 1
}
const pageSize = ref(49)
const currentPage = ref(1)
const filterIcons = (icons: string[]) => {
const start = (unref(currentPage) - 1) * unref(pageSize)
const end = unref(currentPage) * unref(pageSize)
return icons.slice(start, end)
}
watch(
() => modelValue.value,
async (val) => {
await nextTick()
val && init(val)
},
{
immediate: true
}
)
const popoverShow = () => {
init(unref(modelValue))
}
const iconSelect = (icon: string) => {
// 如果是同一个icon则不做处理则相当于点击了清空按钮
if (icon === unref(modelValue)) {
modelValue.value = ''
return
}
modelValue.value = icon
}
const search = ref('')
const filterItemIcons = (icons: string[]) => {
return icons.filter((item) => item.includes(unref(search)))
}
const inputClear = () => {
init(unref(modelValue))
}
</script>
<template>
<div :class="prefixCls" class="flex justify-center items-center box">
<ElInput disabled v-model="modelValue" clearable />
<ElPopover
placement="bottom"
trigger="click"
:width="450"
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; height: 380px;"
@show="popoverShow"
>
<template #reference>
<div :style="iconWrapStyle">
<Icon v-if="modelValue" :icon="modelValue" />
</div>
</template>
<ElScrollbar class="h-[calc(100%-50px)]!">
<ElInput
v-model="search"
class="mb-20px"
clearable
placeholder="搜索图标"
@clear="inputClear"
/>
<ElTabs tab-position="left" v-model="iconName" @tab-change="tabChange">
<ElTabPane v-for="item in icons" :key="item.name" :label="item.name" :name="item.prefix">
<div class="flex flex-wrap box-border">
<div
v-for="icon in filterIcons(filterItemIcons(item.icons))"
:key="icon"
:style="{
width: iconSize,
height: iconSize,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
border: `1px solid ${
icon === modelValue ? 'var(--el-color-primary)' : 'var(--el-border-color)'
}`,
boxSizing: 'border-box',
margin: '2px',
transition: 'all 0.3s'
}"
class="hover:border-color-[var(--el-color-primary)]!"
@click="iconSelect(icon)"
>
<Icon
:icon="icon"
:color="icon === modelValue ? 'var(--el-color-primary)' : 'inherit'"
/>
</div>
</div>
</ElTabPane>
</ElTabs>
</ElScrollbar>
<div
class="h-50px absolute bottom-0 left-0 flex items-center pl-[var(--el-popover-padding)] pr-[var(--el-popover-padding)]"
>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:pager-count="5"
size="small"
:page-sizes="[100, 200, 300, 400]"
layout="total, prev, pager, next, jumper"
:total="filterItemIcons(icons[currentIconNameIndex].icons).length"
/>
</div>
</ElPopover>
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{adminNamespace}-icon-picker';
.@{prefix-cls} {
:deep(.@{elNamespace}-input__wrapper) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>

View File

@@ -0,0 +1,836 @@
export default {
name: 'Ant Design Icons',
prefix: 'vi-ant-design',
icons: [
'vi-ant-design:account-book-filled',
'vi-ant-design:account-book-outlined',
'vi-ant-design:account-book-twotone',
'vi-ant-design:aim-outlined',
'vi-ant-design:alert-filled',
'vi-ant-design:alert-outlined',
'vi-ant-design:alert-twotone',
'vi-ant-design:alibaba-outlined',
'vi-ant-design:align-center-outlined',
'vi-ant-design:align-left-outlined',
'vi-ant-design:align-right-outlined',
'vi-ant-design:alipay-circle-filled',
'vi-ant-design:alipay-circle-outlined',
'vi-ant-design:alipay-outlined',
'vi-ant-design:alipay-square-filled',
'vi-ant-design:aliwangwang-filled',
'vi-ant-design:aliwangwang-outlined',
'vi-ant-design:aliyun-outlined',
'vi-ant-design:amazon-circle-filled',
'vi-ant-design:amazon-outlined',
'vi-ant-design:amazon-square-filled',
'vi-ant-design:android-filled',
'vi-ant-design:android-outlined',
'vi-ant-design:ant-cloud-outlined',
'vi-ant-design:ant-design-outlined',
'vi-ant-design:apartment-outlined',
'vi-ant-design:api-filled',
'vi-ant-design:api-outlined',
'vi-ant-design:api-twotone',
'vi-ant-design:apple-filled',
'vi-ant-design:apple-outlined',
'vi-ant-design:appstore-add-outlined',
'vi-ant-design:appstore-filled',
'vi-ant-design:appstore-outlined',
'vi-ant-design:appstore-twotone',
'vi-ant-design:area-chart-outlined',
'vi-ant-design:arrow-down-outlined',
'vi-ant-design:arrow-left-outlined',
'vi-ant-design:arrow-right-outlined',
'vi-ant-design:arrow-up-outlined',
'vi-ant-design:arrows-alt-outlined',
'vi-ant-design:audio-filled',
'vi-ant-design:audio-muted-outlined',
'vi-ant-design:audio-outlined',
'vi-ant-design:audio-twotone',
'vi-ant-design:audit-outlined',
'vi-ant-design:backward-filled',
'vi-ant-design:backward-outlined',
'vi-ant-design:baidu-outlined',
'vi-ant-design:bank-filled',
'vi-ant-design:bank-outlined',
'vi-ant-design:bank-twotone',
'vi-ant-design:bar-chart-outlined',
'vi-ant-design:barcode-outlined',
'vi-ant-design:bars-outlined',
'vi-ant-design:behance-circle-filled',
'vi-ant-design:behance-outlined',
'vi-ant-design:behance-square-filled',
'vi-ant-design:behance-square-outlined',
'vi-ant-design:bell-filled',
'vi-ant-design:bell-outlined',
'vi-ant-design:bell-twotone',
'vi-ant-design:bg-colors-outlined',
'vi-ant-design:bilibili-filled',
'vi-ant-design:bilibili-outlined',
'vi-ant-design:block-outlined',
'vi-ant-design:bold-outlined',
'vi-ant-design:book-filled',
'vi-ant-design:book-outlined',
'vi-ant-design:book-twotone',
'vi-ant-design:border-bottom-outlined',
'vi-ant-design:border-horizontal-outlined',
'vi-ant-design:border-inner-outlined',
'vi-ant-design:border-left-outlined',
'vi-ant-design:border-outer-outlined',
'vi-ant-design:border-outlined',
'vi-ant-design:border-right-outlined',
'vi-ant-design:border-top-outlined',
'vi-ant-design:border-verticle-outlined',
'vi-ant-design:borderless-table-outlined',
'vi-ant-design:box-plot-filled',
'vi-ant-design:box-plot-outlined',
'vi-ant-design:box-plot-twotone',
'vi-ant-design:branches-outlined',
'vi-ant-design:bug-filled',
'vi-ant-design:bug-outlined',
'vi-ant-design:bug-twotone',
'vi-ant-design:build-filled',
'vi-ant-design:build-outlined',
'vi-ant-design:build-twotone',
'vi-ant-design:bulb-filled',
'vi-ant-design:bulb-outlined',
'vi-ant-design:bulb-twotone',
'vi-ant-design:calculator-filled',
'vi-ant-design:calculator-outlined',
'vi-ant-design:calculator-twotone',
'vi-ant-design:calendar-filled',
'vi-ant-design:calendar-outlined',
'vi-ant-design:calendar-twotone',
'vi-ant-design:camera-filled',
'vi-ant-design:camera-outlined',
'vi-ant-design:camera-twotone',
'vi-ant-design:car-filled',
'vi-ant-design:car-outlined',
'vi-ant-design:car-twotone',
'vi-ant-design:caret-down-filled',
'vi-ant-design:caret-down-outlined',
'vi-ant-design:caret-left-filled',
'vi-ant-design:caret-left-outlined',
'vi-ant-design:caret-right-filled',
'vi-ant-design:caret-right-outlined',
'vi-ant-design:caret-up-filled',
'vi-ant-design:caret-up-outlined',
'vi-ant-design:carry-out-filled',
'vi-ant-design:carry-out-outlined',
'vi-ant-design:carry-out-twotone',
'vi-ant-design:check-circle-filled',
'vi-ant-design:check-circle-outlined',
'vi-ant-design:check-circle-twotone',
'vi-ant-design:check-outlined',
'vi-ant-design:check-square-filled',
'vi-ant-design:check-square-outlined',
'vi-ant-design:check-square-twotone',
'vi-ant-design:chrome-filled',
'vi-ant-design:chrome-outlined',
'vi-ant-design:ci-circle-filled',
'vi-ant-design:ci-circle-outlined',
'vi-ant-design:ci-circle-twotone',
'vi-ant-design:ci-outlined',
'vi-ant-design:ci-twotone',
'vi-ant-design:clear-outlined',
'vi-ant-design:clock-circle-filled',
'vi-ant-design:clock-circle-outlined',
'vi-ant-design:clock-circle-twotone',
'vi-ant-design:close-circle-filled',
'vi-ant-design:close-circle-outlined',
'vi-ant-design:close-circle-twotone',
'vi-ant-design:close-outlined',
'vi-ant-design:close-square-filled',
'vi-ant-design:close-square-outlined',
'vi-ant-design:close-square-twotone',
'vi-ant-design:cloud-download-outlined',
'vi-ant-design:cloud-filled',
'vi-ant-design:cloud-outlined',
'vi-ant-design:cloud-server-outlined',
'vi-ant-design:cloud-sync-outlined',
'vi-ant-design:cloud-twotone',
'vi-ant-design:cloud-upload-outlined',
'vi-ant-design:cluster-outlined',
'vi-ant-design:code-filled',
'vi-ant-design:code-outlined',
'vi-ant-design:code-sandbox-circle-filled',
'vi-ant-design:code-sandbox-outlined',
'vi-ant-design:code-sandbox-square-filled',
'vi-ant-design:code-twotone',
'vi-ant-design:codepen-circle-filled',
'vi-ant-design:codepen-circle-outlined',
'vi-ant-design:codepen-outlined',
'vi-ant-design:codepen-square-filled',
'vi-ant-design:coffee-outlined',
'vi-ant-design:column-height-outlined',
'vi-ant-design:column-width-outlined',
'vi-ant-design:comment-outlined',
'vi-ant-design:compass-filled',
'vi-ant-design:compass-outlined',
'vi-ant-design:compass-twotone',
'vi-ant-design:compress-outlined',
'vi-ant-design:console-sql-outlined',
'vi-ant-design:contacts-filled',
'vi-ant-design:contacts-outlined',
'vi-ant-design:contacts-twotone',
'vi-ant-design:container-filled',
'vi-ant-design:container-outlined',
'vi-ant-design:container-twotone',
'vi-ant-design:control-filled',
'vi-ant-design:control-outlined',
'vi-ant-design:control-twotone',
'vi-ant-design:copy-filled',
'vi-ant-design:copy-outlined',
'vi-ant-design:copy-twotone',
'vi-ant-design:copyright-circle-filled',
'vi-ant-design:copyright-circle-outlined',
'vi-ant-design:copyright-circle-twotone',
'vi-ant-design:copyright-outlined',
'vi-ant-design:copyright-twotone',
'vi-ant-design:credit-card-filled',
'vi-ant-design:credit-card-outlined',
'vi-ant-design:credit-card-twotone',
'vi-ant-design:crown-filled',
'vi-ant-design:crown-outlined',
'vi-ant-design:crown-twotone',
'vi-ant-design:customer-service-filled',
'vi-ant-design:customer-service-outlined',
'vi-ant-design:customer-service-twotone',
'vi-ant-design:dash-outlined',
'vi-ant-design:dashboard-filled',
'vi-ant-design:dashboard-outlined',
'vi-ant-design:dashboard-twotone',
'vi-ant-design:database-filled',
'vi-ant-design:database-outlined',
'vi-ant-design:database-twotone',
'vi-ant-design:delete-column-outlined',
'vi-ant-design:delete-filled',
'vi-ant-design:delete-outlined',
'vi-ant-design:delete-row-outlined',
'vi-ant-design:delete-twotone',
'vi-ant-design:delivered-procedure-outlined',
'vi-ant-design:deployment-unit-outlined',
'vi-ant-design:desktop-outlined',
'vi-ant-design:diff-filled',
'vi-ant-design:diff-outlined',
'vi-ant-design:diff-twotone',
'vi-ant-design:dingding-outlined',
'vi-ant-design:dingtalk-circle-filled',
'vi-ant-design:dingtalk-outlined',
'vi-ant-design:dingtalk-square-filled',
'vi-ant-design:disconnect-outlined',
'vi-ant-design:discord-filled',
'vi-ant-design:discord-outlined',
'vi-ant-design:dislike-filled',
'vi-ant-design:dislike-outlined',
'vi-ant-design:dislike-twotone',
'vi-ant-design:docker-outlined',
'vi-ant-design:dollar-circle-filled',
'vi-ant-design:dollar-circle-outlined',
'vi-ant-design:dollar-circle-twotone',
'vi-ant-design:dollar-outlined',
'vi-ant-design:dollar-twotone',
'vi-ant-design:dot-chart-outlined',
'vi-ant-design:dot-net-outlined',
'vi-ant-design:double-left-outlined',
'vi-ant-design:double-right-outlined',
'vi-ant-design:down-circle-filled',
'vi-ant-design:down-circle-outlined',
'vi-ant-design:down-circle-twotone',
'vi-ant-design:down-outlined',
'vi-ant-design:down-square-filled',
'vi-ant-design:down-square-outlined',
'vi-ant-design:down-square-twotone',
'vi-ant-design:download-outlined',
'vi-ant-design:drag-outlined',
'vi-ant-design:dribbble-circle-filled',
'vi-ant-design:dribbble-outlined',
'vi-ant-design:dribbble-square-filled',
'vi-ant-design:dribbble-square-outlined',
'vi-ant-design:dropbox-circle-filled',
'vi-ant-design:dropbox-outlined',
'vi-ant-design:dropbox-square-filled',
'vi-ant-design:edit-filled',
'vi-ant-design:edit-outlined',
'vi-ant-design:edit-twotone',
'vi-ant-design:ellipsis-outlined',
'vi-ant-design:enter-outlined',
'vi-ant-design:environment-filled',
'vi-ant-design:environment-outlined',
'vi-ant-design:environment-twotone',
'vi-ant-design:euro-circle-filled',
'vi-ant-design:euro-circle-outlined',
'vi-ant-design:euro-circle-twotone',
'vi-ant-design:euro-outlined',
'vi-ant-design:euro-twotone',
'vi-ant-design:exception-outlined',
'vi-ant-design:exclamation-circle-filled',
'vi-ant-design:exclamation-circle-outlined',
'vi-ant-design:exclamation-circle-twotone',
'vi-ant-design:exclamation-outlined',
'vi-ant-design:expand-alt-outlined',
'vi-ant-design:expand-outlined',
'vi-ant-design:experiment-filled',
'vi-ant-design:experiment-outlined',
'vi-ant-design:experiment-twotone',
'vi-ant-design:export-outlined',
'vi-ant-design:eye-filled',
'vi-ant-design:eye-invisible-filled',
'vi-ant-design:eye-invisible-outlined',
'vi-ant-design:eye-invisible-twotone',
'vi-ant-design:eye-outlined',
'vi-ant-design:eye-twotone',
'vi-ant-design:facebook-filled',
'vi-ant-design:facebook-outlined',
'vi-ant-design:fall-outlined',
'vi-ant-design:fast-backward-filled',
'vi-ant-design:fast-backward-outlined',
'vi-ant-design:fast-forward-filled',
'vi-ant-design:fast-forward-outlined',
'vi-ant-design:field-binary-outlined',
'vi-ant-design:field-number-outlined',
'vi-ant-design:field-string-outlined',
'vi-ant-design:field-time-outlined',
'vi-ant-design:file-add-filled',
'vi-ant-design:file-add-outlined',
'vi-ant-design:file-add-twotone',
'vi-ant-design:file-done-outlined',
'vi-ant-design:file-excel-filled',
'vi-ant-design:file-excel-outlined',
'vi-ant-design:file-excel-twotone',
'vi-ant-design:file-exclamation-filled',
'vi-ant-design:file-exclamation-outlined',
'vi-ant-design:file-exclamation-twotone',
'vi-ant-design:file-filled',
'vi-ant-design:file-gif-outlined',
'vi-ant-design:file-image-filled',
'vi-ant-design:file-image-outlined',
'vi-ant-design:file-image-twotone',
'vi-ant-design:file-jpg-outlined',
'vi-ant-design:file-markdown-filled',
'vi-ant-design:file-markdown-outlined',
'vi-ant-design:file-markdown-twotone',
'vi-ant-design:file-outlined',
'vi-ant-design:file-pdf-filled',
'vi-ant-design:file-pdf-outlined',
'vi-ant-design:file-pdf-twotone',
'vi-ant-design:file-ppt-filled',
'vi-ant-design:file-ppt-outlined',
'vi-ant-design:file-ppt-twotone',
'vi-ant-design:file-protect-outlined',
'vi-ant-design:file-search-outlined',
'vi-ant-design:file-sync-outlined',
'vi-ant-design:file-text-filled',
'vi-ant-design:file-text-outlined',
'vi-ant-design:file-text-twotone',
'vi-ant-design:file-twotone',
'vi-ant-design:file-unknown-filled',
'vi-ant-design:file-unknown-outlined',
'vi-ant-design:file-unknown-twotone',
'vi-ant-design:file-word-filled',
'vi-ant-design:file-word-outlined',
'vi-ant-design:file-word-twotone',
'vi-ant-design:file-zip-filled',
'vi-ant-design:file-zip-outlined',
'vi-ant-design:file-zip-twotone',
'vi-ant-design:filter-filled',
'vi-ant-design:filter-outlined',
'vi-ant-design:filter-twotone',
'vi-ant-design:fire-filled',
'vi-ant-design:fire-outlined',
'vi-ant-design:fire-twotone',
'vi-ant-design:flag-filled',
'vi-ant-design:flag-outlined',
'vi-ant-design:flag-twotone',
'vi-ant-design:folder-add-filled',
'vi-ant-design:folder-add-outlined',
'vi-ant-design:folder-add-twotone',
'vi-ant-design:folder-filled',
'vi-ant-design:folder-open-filled',
'vi-ant-design:folder-open-outlined',
'vi-ant-design:folder-open-twotone',
'vi-ant-design:folder-outlined',
'vi-ant-design:folder-twotone',
'vi-ant-design:folder-view-outlined',
'vi-ant-design:font-colors-outlined',
'vi-ant-design:font-size-outlined',
'vi-ant-design:fork-outlined',
'vi-ant-design:form-outlined',
'vi-ant-design:format-painter-filled',
'vi-ant-design:format-painter-outlined',
'vi-ant-design:forward-filled',
'vi-ant-design:forward-outlined',
'vi-ant-design:frown-filled',
'vi-ant-design:frown-outlined',
'vi-ant-design:frown-twotone',
'vi-ant-design:fullscreen-exit-outlined',
'vi-ant-design:fullscreen-outlined',
'vi-ant-design:function-outlined',
'vi-ant-design:fund-filled',
'vi-ant-design:fund-outlined',
'vi-ant-design:fund-projection-screen-outlined',
'vi-ant-design:fund-twotone',
'vi-ant-design:fund-view-outlined',
'vi-ant-design:funnel-plot-filled',
'vi-ant-design:funnel-plot-outlined',
'vi-ant-design:funnel-plot-twotone',
'vi-ant-design:gateway-outlined',
'vi-ant-design:gif-outlined',
'vi-ant-design:gift-filled',
'vi-ant-design:gift-outlined',
'vi-ant-design:gift-twotone',
'vi-ant-design:github-filled',
'vi-ant-design:github-outlined',
'vi-ant-design:gitlab-filled',
'vi-ant-design:gitlab-outlined',
'vi-ant-design:global-outlined',
'vi-ant-design:gold-filled',
'vi-ant-design:gold-outlined',
'vi-ant-design:gold-twotone',
'vi-ant-design:golden-filled',
'vi-ant-design:google-circle-filled',
'vi-ant-design:google-outlined',
'vi-ant-design:google-plus-circle-filled',
'vi-ant-design:google-plus-outlined',
'vi-ant-design:google-plus-square-filled',
'vi-ant-design:google-square-filled',
'vi-ant-design:group-outlined',
'vi-ant-design:harmony-o-s-outlined',
'vi-ant-design:hdd-filled',
'vi-ant-design:hdd-outlined',
'vi-ant-design:hdd-twotone',
'vi-ant-design:heart-filled',
'vi-ant-design:heart-outlined',
'vi-ant-design:heart-twotone',
'vi-ant-design:heat-map-outlined',
'vi-ant-design:highlight-filled',
'vi-ant-design:highlight-outlined',
'vi-ant-design:highlight-twotone',
'vi-ant-design:history-outlined',
'vi-ant-design:holder-outlined',
'vi-ant-design:home-filled',
'vi-ant-design:home-outlined',
'vi-ant-design:home-twotone',
'vi-ant-design:hourglass-filled',
'vi-ant-design:hourglass-outlined',
'vi-ant-design:hourglass-twotone',
'vi-ant-design:html5-filled',
'vi-ant-design:html5-outlined',
'vi-ant-design:html5-twotone',
'vi-ant-design:idcard-filled',
'vi-ant-design:idcard-outlined',
'vi-ant-design:idcard-twotone',
'vi-ant-design:ie-circle-filled',
'vi-ant-design:ie-outlined',
'vi-ant-design:ie-square-filled',
'vi-ant-design:import-outlined',
'vi-ant-design:inbox-outlined',
'vi-ant-design:info-circle-filled',
'vi-ant-design:info-circle-outlined',
'vi-ant-design:info-circle-twotone',
'vi-ant-design:info-outlined',
'vi-ant-design:insert-row-above-outlined',
'vi-ant-design:insert-row-below-outlined',
'vi-ant-design:insert-row-left-outlined',
'vi-ant-design:insert-row-right-outlined',
'vi-ant-design:instagram-filled',
'vi-ant-design:instagram-outlined',
'vi-ant-design:insurance-filled',
'vi-ant-design:insurance-outlined',
'vi-ant-design:insurance-twotone',
'vi-ant-design:interaction-filled',
'vi-ant-design:interaction-outlined',
'vi-ant-design:interaction-twotone',
'vi-ant-design:issues-close-outlined',
'vi-ant-design:italic-outlined',
'vi-ant-design:java-outlined',
'vi-ant-design:java-script-outlined',
'vi-ant-design:key-outlined',
'vi-ant-design:kubernetes-outlined',
'vi-ant-design:laptop-outlined',
'vi-ant-design:layout-filled',
'vi-ant-design:layout-outlined',
'vi-ant-design:layout-twotone',
'vi-ant-design:left-circle-filled',
'vi-ant-design:left-circle-outlined',
'vi-ant-design:left-circle-twotone',
'vi-ant-design:left-outlined',
'vi-ant-design:left-square-filled',
'vi-ant-design:left-square-outlined',
'vi-ant-design:left-square-twotone',
'vi-ant-design:like-filled',
'vi-ant-design:like-outlined',
'vi-ant-design:like-twotone',
'vi-ant-design:line-chart-outlined',
'vi-ant-design:line-height-outlined',
'vi-ant-design:line-outlined',
'vi-ant-design:link-outlined',
'vi-ant-design:linkedin-filled',
'vi-ant-design:linkedin-outlined',
'vi-ant-design:linux-outlined',
'vi-ant-design:loading-3-quarters-outlined',
'vi-ant-design:loading-outlined',
'vi-ant-design:lock-filled',
'vi-ant-design:lock-outlined',
'vi-ant-design:lock-twotone',
'vi-ant-design:login-outlined',
'vi-ant-design:logout-outlined',
'vi-ant-design:mac-command-filled',
'vi-ant-design:mac-command-outlined',
'vi-ant-design:mail-filled',
'vi-ant-design:mail-outlined',
'vi-ant-design:mail-twotone',
'vi-ant-design:man-outlined',
'vi-ant-design:medicine-box-filled',
'vi-ant-design:medicine-box-outlined',
'vi-ant-design:medicine-box-twotone',
'vi-ant-design:medium-circle-filled',
'vi-ant-design:medium-outlined',
'vi-ant-design:medium-square-filled',
'vi-ant-design:medium-workmark-outlined',
'vi-ant-design:meh-filled',
'vi-ant-design:meh-outlined',
'vi-ant-design:meh-twotone',
'vi-ant-design:menu-fold-outlined',
'vi-ant-design:menu-outlined',
'vi-ant-design:menu-unfold-outlined',
'vi-ant-design:merge-cells-outlined',
'vi-ant-design:merge-filled',
'vi-ant-design:merge-outlined',
'vi-ant-design:message-filled',
'vi-ant-design:message-outlined',
'vi-ant-design:message-twotone',
'vi-ant-design:minus-circle-filled',
'vi-ant-design:minus-circle-outlined',
'vi-ant-design:minus-circle-twotone',
'vi-ant-design:minus-outlined',
'vi-ant-design:minus-square-filled',
'vi-ant-design:minus-square-outlined',
'vi-ant-design:minus-square-twotone',
'vi-ant-design:mobile-filled',
'vi-ant-design:mobile-outlined',
'vi-ant-design:mobile-twotone',
'vi-ant-design:money-collect-filled',
'vi-ant-design:money-collect-outlined',
'vi-ant-design:money-collect-twotone',
'vi-ant-design:monitor-outlined',
'vi-ant-design:moon-filled',
'vi-ant-design:moon-outlined',
'vi-ant-design:more-outlined',
'vi-ant-design:muted-filled',
'vi-ant-design:muted-outlined',
'vi-ant-design:node-collapse-outlined',
'vi-ant-design:node-expand-outlined',
'vi-ant-design:node-index-outlined',
'vi-ant-design:notification-filled',
'vi-ant-design:notification-outlined',
'vi-ant-design:notification-twotone',
'vi-ant-design:number-outlined',
'vi-ant-design:one-to-one-outlined',
'vi-ant-design:open-a-i-filled',
'vi-ant-design:open-a-i-outlined',
'vi-ant-design:ordered-list-outlined',
'vi-ant-design:paper-clip-outlined',
'vi-ant-design:partition-outlined',
'vi-ant-design:pause-circle-filled',
'vi-ant-design:pause-circle-outlined',
'vi-ant-design:pause-circle-twotone',
'vi-ant-design:pause-outlined',
'vi-ant-design:pay-circle-filled',
'vi-ant-design:pay-circle-outlined',
'vi-ant-design:percentage-outlined',
'vi-ant-design:phone-filled',
'vi-ant-design:phone-outlined',
'vi-ant-design:phone-twotone',
'vi-ant-design:pic-center-outlined',
'vi-ant-design:pic-left-outlined',
'vi-ant-design:pic-right-outlined',
'vi-ant-design:picture-filled',
'vi-ant-design:picture-outlined',
'vi-ant-design:picture-twotone',
'vi-ant-design:pie-chart-filled',
'vi-ant-design:pie-chart-outlined',
'vi-ant-design:pie-chart-twotone',
'vi-ant-design:pinterest-filled',
'vi-ant-design:pinterest-outlined',
'vi-ant-design:play-circle-filled',
'vi-ant-design:play-circle-outlined',
'vi-ant-design:play-circle-twotone',
'vi-ant-design:play-square-filled',
'vi-ant-design:play-square-outlined',
'vi-ant-design:play-square-twotone',
'vi-ant-design:plus-circle-filled',
'vi-ant-design:plus-circle-outlined',
'vi-ant-design:plus-circle-twotone',
'vi-ant-design:plus-outlined',
'vi-ant-design:plus-square-filled',
'vi-ant-design:plus-square-outlined',
'vi-ant-design:plus-square-twotone',
'vi-ant-design:pound-circle-filled',
'vi-ant-design:pound-circle-outlined',
'vi-ant-design:pound-circle-twotone',
'vi-ant-design:pound-outlined',
'vi-ant-design:poweroff-outlined',
'vi-ant-design:printer-filled',
'vi-ant-design:printer-outlined',
'vi-ant-design:printer-twotone',
'vi-ant-design:product-filled',
'vi-ant-design:product-outlined',
'vi-ant-design:profile-filled',
'vi-ant-design:profile-outlined',
'vi-ant-design:profile-twotone',
'vi-ant-design:project-filled',
'vi-ant-design:project-outlined',
'vi-ant-design:project-twotone',
'vi-ant-design:property-safety-filled',
'vi-ant-design:property-safety-outlined',
'vi-ant-design:property-safety-twotone',
'vi-ant-design:pull-request-outlined',
'vi-ant-design:pushpin-filled',
'vi-ant-design:pushpin-outlined',
'vi-ant-design:pushpin-twotone',
'vi-ant-design:python-outlined',
'vi-ant-design:qq-circle-filled',
'vi-ant-design:qq-outlined',
'vi-ant-design:qq-square-filled',
'vi-ant-design:qrcode-outlined',
'vi-ant-design:question-circle-filled',
'vi-ant-design:question-circle-outlined',
'vi-ant-design:question-circle-twotone',
'vi-ant-design:question-outlined',
'vi-ant-design:radar-chart-outlined',
'vi-ant-design:radius-bottomleft-outlined',
'vi-ant-design:radius-bottomright-outlined',
'vi-ant-design:radius-setting-outlined',
'vi-ant-design:radius-upleft-outlined',
'vi-ant-design:radius-upright-outlined',
'vi-ant-design:read-filled',
'vi-ant-design:read-outlined',
'vi-ant-design:reconciliation-filled',
'vi-ant-design:reconciliation-outlined',
'vi-ant-design:reconciliation-twotone',
'vi-ant-design:red-envelope-filled',
'vi-ant-design:red-envelope-outlined',
'vi-ant-design:red-envelope-twotone',
'vi-ant-design:reddit-circle-filled',
'vi-ant-design:reddit-outlined',
'vi-ant-design:reddit-square-filled',
'vi-ant-design:redo-outlined',
'vi-ant-design:reload-outlined',
'vi-ant-design:rest-filled',
'vi-ant-design:rest-outlined',
'vi-ant-design:rest-twotone',
'vi-ant-design:retweet-outlined',
'vi-ant-design:right-circle-filled',
'vi-ant-design:right-circle-outlined',
'vi-ant-design:right-circle-twotone',
'vi-ant-design:right-outlined',
'vi-ant-design:right-square-filled',
'vi-ant-design:right-square-outlined',
'vi-ant-design:right-square-twotone',
'vi-ant-design:rise-outlined',
'vi-ant-design:robot-filled',
'vi-ant-design:robot-outlined',
'vi-ant-design:rocket-filled',
'vi-ant-design:rocket-outlined',
'vi-ant-design:rocket-twotone',
'vi-ant-design:rollback-outlined',
'vi-ant-design:rotate-left-outlined',
'vi-ant-design:rotate-right-outlined',
'vi-ant-design:ruby-outlined',
'vi-ant-design:safety-certificate-filled',
'vi-ant-design:safety-certificate-outlined',
'vi-ant-design:safety-certificate-twotone',
'vi-ant-design:safety-outlined',
'vi-ant-design:save-filled',
'vi-ant-design:save-outlined',
'vi-ant-design:save-twotone',
'vi-ant-design:scan-outlined',
'vi-ant-design:schedule-filled',
'vi-ant-design:schedule-outlined',
'vi-ant-design:schedule-twotone',
'vi-ant-design:scissor-outlined',
'vi-ant-design:search-outlined',
'vi-ant-design:security-scan-filled',
'vi-ant-design:security-scan-outlined',
'vi-ant-design:security-scan-twotone',
'vi-ant-design:select-outlined',
'vi-ant-design:send-outlined',
'vi-ant-design:setting-filled',
'vi-ant-design:setting-outlined',
'vi-ant-design:setting-twotone',
'vi-ant-design:shake-outlined',
'vi-ant-design:share-alt-outlined',
'vi-ant-design:shop-filled',
'vi-ant-design:shop-outlined',
'vi-ant-design:shop-twotone',
'vi-ant-design:shopping-cart-outlined',
'vi-ant-design:shopping-filled',
'vi-ant-design:shopping-outlined',
'vi-ant-design:shopping-twotone',
'vi-ant-design:shrink-outlined',
'vi-ant-design:signal-filled',
'vi-ant-design:signature-filled',
'vi-ant-design:signature-outlined',
'vi-ant-design:sisternode-outlined',
'vi-ant-design:sketch-circle-filled',
'vi-ant-design:sketch-outlined',
'vi-ant-design:sketch-square-filled',
'vi-ant-design:skin-filled',
'vi-ant-design:skin-outlined',
'vi-ant-design:skin-twotone',
'vi-ant-design:skype-filled',
'vi-ant-design:skype-outlined',
'vi-ant-design:slack-circle-filled',
'vi-ant-design:slack-outlined',
'vi-ant-design:slack-square-filled',
'vi-ant-design:slack-square-outlined',
'vi-ant-design:sliders-filled',
'vi-ant-design:sliders-outlined',
'vi-ant-design:sliders-twotone',
'vi-ant-design:small-dash-outlined',
'vi-ant-design:smile-filled',
'vi-ant-design:smile-outlined',
'vi-ant-design:smile-twotone',
'vi-ant-design:snippets-filled',
'vi-ant-design:snippets-outlined',
'vi-ant-design:snippets-twotone',
'vi-ant-design:solution-outlined',
'vi-ant-design:sort-ascending-outlined',
'vi-ant-design:sort-descending-outlined',
'vi-ant-design:sound-filled',
'vi-ant-design:sound-outlined',
'vi-ant-design:sound-twotone',
'vi-ant-design:split-cells-outlined',
'vi-ant-design:spotify-filled',
'vi-ant-design:spotify-outlined',
'vi-ant-design:star-filled',
'vi-ant-design:star-outlined',
'vi-ant-design:star-twotone',
'vi-ant-design:step-backward-filled',
'vi-ant-design:step-backward-outlined',
'vi-ant-design:step-forward-filled',
'vi-ant-design:step-forward-outlined',
'vi-ant-design:stock-outlined',
'vi-ant-design:stop-filled',
'vi-ant-design:stop-outlined',
'vi-ant-design:stop-twotone',
'vi-ant-design:strikethrough-outlined',
'vi-ant-design:subnode-outlined',
'vi-ant-design:sun-filled',
'vi-ant-design:sun-outlined',
'vi-ant-design:swap-left-outlined',
'vi-ant-design:swap-outlined',
'vi-ant-design:swap-right-outlined',
'vi-ant-design:switcher-filled',
'vi-ant-design:switcher-outlined',
'vi-ant-design:switcher-twotone',
'vi-ant-design:sync-outlined',
'vi-ant-design:table-outlined',
'vi-ant-design:tablet-filled',
'vi-ant-design:tablet-outlined',
'vi-ant-design:tablet-twotone',
'vi-ant-design:tag-filled',
'vi-ant-design:tag-outlined',
'vi-ant-design:tag-twotone',
'vi-ant-design:tags-filled',
'vi-ant-design:tags-outlined',
'vi-ant-design:tags-twotone',
'vi-ant-design:taobao-circle-filled',
'vi-ant-design:taobao-circle-outlined',
'vi-ant-design:taobao-outlined',
'vi-ant-design:taobao-square-filled',
'vi-ant-design:team-outlined',
'vi-ant-design:thunderbolt-filled',
'vi-ant-design:thunderbolt-outlined',
'vi-ant-design:thunderbolt-twotone',
'vi-ant-design:tik-tok-filled',
'vi-ant-design:tik-tok-outlined',
'vi-ant-design:to-top-outlined',
'vi-ant-design:tool-filled',
'vi-ant-design:tool-outlined',
'vi-ant-design:tool-twotone',
'vi-ant-design:trademark-circle-filled',
'vi-ant-design:trademark-circle-outlined',
'vi-ant-design:trademark-circle-twotone',
'vi-ant-design:trademark-outlined',
'vi-ant-design:transaction-outlined',
'vi-ant-design:translation-outlined',
'vi-ant-design:trophy-filled',
'vi-ant-design:trophy-outlined',
'vi-ant-design:trophy-twotone',
'vi-ant-design:truck-filled',
'vi-ant-design:truck-outlined',
'vi-ant-design:twitch-outlined',
'vi-ant-design:twitter-circle-filled',
'vi-ant-design:twitter-outlined',
'vi-ant-design:twitter-square-filled',
'vi-ant-design:underline-outlined',
'vi-ant-design:undo-outlined',
'vi-ant-design:ungroup-outlined',
'vi-ant-design:unlock-filled',
'vi-ant-design:unlock-outlined',
'vi-ant-design:unlock-twotone',
'vi-ant-design:unordered-list-outlined',
'vi-ant-design:up-circle-filled',
'vi-ant-design:up-circle-outlined',
'vi-ant-design:up-circle-twotone',
'vi-ant-design:up-outlined',
'vi-ant-design:up-square-filled',
'vi-ant-design:up-square-outlined',
'vi-ant-design:up-square-twotone',
'vi-ant-design:upload-outlined',
'vi-ant-design:usb-filled',
'vi-ant-design:usb-outlined',
'vi-ant-design:usb-twotone',
'vi-ant-design:user-add-outlined',
'vi-ant-design:user-delete-outlined',
'vi-ant-design:user-outlined',
'vi-ant-design:user-switch-outlined',
'vi-ant-design:usergroup-add-outlined',
'vi-ant-design:usergroup-delete-outlined',
'vi-ant-design:verified-outlined',
'vi-ant-design:vertical-align-bottom-outlined',
'vi-ant-design:vertical-align-middle-outlined',
'vi-ant-design:vertical-align-top-outlined',
'vi-ant-design:vertical-left-outlined',
'vi-ant-design:vertical-right-outlined',
'vi-ant-design:video-camera-add-outlined',
'vi-ant-design:video-camera-filled',
'vi-ant-design:video-camera-outlined',
'vi-ant-design:video-camera-twotone',
'vi-ant-design:wallet-filled',
'vi-ant-design:wallet-outlined',
'vi-ant-design:wallet-twotone',
'vi-ant-design:warning-filled',
'vi-ant-design:warning-outlined',
'vi-ant-design:warning-twotone',
'vi-ant-design:wechat-filled',
'vi-ant-design:wechat-outlined',
'vi-ant-design:wechat-work-filled',
'vi-ant-design:wechat-work-outlined',
'vi-ant-design:weibo-circle-filled',
'vi-ant-design:weibo-circle-outlined',
'vi-ant-design:weibo-outlined',
'vi-ant-design:weibo-square-filled',
'vi-ant-design:weibo-square-outlined',
'vi-ant-design:whats-app-outlined',
'vi-ant-design:wifi-outlined',
'vi-ant-design:windows-filled',
'vi-ant-design:windows-outlined',
'vi-ant-design:woman-outlined',
'vi-ant-design:x-filled',
'vi-ant-design:x-outlined',
'vi-ant-design:yahoo-filled',
'vi-ant-design:yahoo-outlined',
'vi-ant-design:youtube-filled',
'vi-ant-design:youtube-outlined',
'vi-ant-design:yuque-filled',
'vi-ant-design:yuque-outlined',
'vi-ant-design:zhihu-circle-filled',
'vi-ant-design:zhihu-outlined',
'vi-ant-design:zhihu-square-filled',
'vi-ant-design:zoom-in-outlined',
'vi-ant-design:zoom-out-outlined'
]
}

View File

@@ -0,0 +1,299 @@
export default {
name: 'Element Plus',
prefix: 'vi-ep',
icons: [
'vi-ep:add-location',
'vi-ep:aim',
'vi-ep:alarm-clock',
'vi-ep:apple',
'vi-ep:arrow-down',
'vi-ep:arrow-down-bold',
'vi-ep:arrow-left',
'vi-ep:arrow-left-bold',
'vi-ep:arrow-right',
'vi-ep:arrow-right-bold',
'vi-ep:arrow-up',
'vi-ep:arrow-up-bold',
'vi-ep:avatar',
'vi-ep:back',
'vi-ep:baseball',
'vi-ep:basketball',
'vi-ep:bell',
'vi-ep:bell-filled',
'vi-ep:bicycle',
'vi-ep:bottom',
'vi-ep:bottom-left',
'vi-ep:bottom-right',
'vi-ep:bowl',
'vi-ep:box',
'vi-ep:briefcase',
'vi-ep:brush',
'vi-ep:brush-filled',
'vi-ep:burger',
'vi-ep:calendar',
'vi-ep:camera',
'vi-ep:camera-filled',
'vi-ep:caret-bottom',
'vi-ep:caret-left',
'vi-ep:caret-right',
'vi-ep:caret-top',
'vi-ep:cellphone',
'vi-ep:chat-dot-round',
'vi-ep:chat-dot-square',
'vi-ep:chat-line-round',
'vi-ep:chat-line-square',
'vi-ep:chat-round',
'vi-ep:chat-square',
'vi-ep:check',
'vi-ep:checked',
'vi-ep:cherry',
'vi-ep:chicken',
'vi-ep:chrome-filled',
'vi-ep:circle-check',
'vi-ep:circle-check-filled',
'vi-ep:circle-close',
'vi-ep:circle-close-filled',
'vi-ep:circle-plus',
'vi-ep:circle-plus-filled',
'vi-ep:clock',
'vi-ep:close',
'vi-ep:close-bold',
'vi-ep:cloudy',
'vi-ep:coffee',
'vi-ep:coffee-cup',
'vi-ep:coin',
'vi-ep:cold-drink',
'vi-ep:collection',
'vi-ep:collection-tag',
'vi-ep:comment',
'vi-ep:compass',
'vi-ep:connection',
'vi-ep:coordinate',
'vi-ep:copy-document',
'vi-ep:cpu',
'vi-ep:credit-card',
'vi-ep:crop',
'vi-ep:d-arrow-left',
'vi-ep:d-arrow-right',
'vi-ep:d-caret',
'vi-ep:data-analysis',
'vi-ep:data-board',
'vi-ep:data-line',
'vi-ep:delete',
'vi-ep:delete-filled',
'vi-ep:delete-location',
'vi-ep:dessert',
'vi-ep:discount',
'vi-ep:dish',
'vi-ep:dish-dot',
'vi-ep:document',
'vi-ep:document-add',
'vi-ep:document-checked',
'vi-ep:document-copy',
'vi-ep:document-delete',
'vi-ep:document-remove',
'vi-ep:download',
'vi-ep:drizzling',
'vi-ep:edit',
'vi-ep:edit-pen',
'vi-ep:eleme',
'vi-ep:eleme-filled',
'vi-ep:element-plus',
'vi-ep:expand',
'vi-ep:failed',
'vi-ep:female',
'vi-ep:files',
'vi-ep:film',
'vi-ep:filter',
'vi-ep:finished',
'vi-ep:first-aid-kit',
'vi-ep:flag',
'vi-ep:fold',
'vi-ep:folder',
'vi-ep:folder-add',
'vi-ep:folder-checked',
'vi-ep:folder-delete',
'vi-ep:folder-opened',
'vi-ep:folder-remove',
'vi-ep:food',
'vi-ep:football',
'vi-ep:fork-spoon',
'vi-ep:fries',
'vi-ep:full-screen',
'vi-ep:goblet',
'vi-ep:goblet-full',
'vi-ep:goblet-square',
'vi-ep:goblet-square-full',
'vi-ep:gold-medal',
'vi-ep:goods',
'vi-ep:goods-filled',
'vi-ep:grape',
'vi-ep:grid',
'vi-ep:guide',
'vi-ep:handbag',
'vi-ep:headset',
'vi-ep:help',
'vi-ep:help-filled',
'vi-ep:hide',
'vi-ep:histogram',
'vi-ep:home-filled',
'vi-ep:hot-water',
'vi-ep:house',
'vi-ep:ice-cream',
'vi-ep:ice-cream-round',
'vi-ep:ice-cream-square',
'vi-ep:ice-drink',
'vi-ep:ice-tea',
'vi-ep:info-filled',
'vi-ep:iphone',
'vi-ep:key',
'vi-ep:knife-fork',
'vi-ep:lightning',
'vi-ep:link',
'vi-ep:list',
'vi-ep:loading',
'vi-ep:location',
'vi-ep:location-filled',
'vi-ep:location-information',
'vi-ep:lock',
'vi-ep:lollipop',
'vi-ep:magic-stick',
'vi-ep:magnet',
'vi-ep:male',
'vi-ep:management',
'vi-ep:map-location',
'vi-ep:medal',
'vi-ep:memo',
'vi-ep:menu',
'vi-ep:message',
'vi-ep:message-box',
'vi-ep:mic',
'vi-ep:microphone',
'vi-ep:milk-tea',
'vi-ep:minus',
'vi-ep:money',
'vi-ep:monitor',
'vi-ep:moon',
'vi-ep:moon-night',
'vi-ep:more',
'vi-ep:more-filled',
'vi-ep:mostly-cloudy',
'vi-ep:mouse',
'vi-ep:mug',
'vi-ep:mute',
'vi-ep:mute-notification',
'vi-ep:no-smoking',
'vi-ep:notebook',
'vi-ep:notification',
'vi-ep:odometer',
'vi-ep:office-building',
'vi-ep:open',
'vi-ep:operation',
'vi-ep:opportunity',
'vi-ep:orange',
'vi-ep:paperclip',
'vi-ep:partly-cloudy',
'vi-ep:pear',
'vi-ep:phone',
'vi-ep:phone-filled',
'vi-ep:picture',
'vi-ep:picture-filled',
'vi-ep:picture-rounded',
'vi-ep:pie-chart',
'vi-ep:place',
'vi-ep:platform',
'vi-ep:plus',
'vi-ep:pointer',
'vi-ep:position',
'vi-ep:postcard',
'vi-ep:pouring',
'vi-ep:present',
'vi-ep:price-tag',
'vi-ep:printer',
'vi-ep:promotion',
'vi-ep:quartz-watch',
'vi-ep:question-filled',
'vi-ep:rank',
'vi-ep:reading',
'vi-ep:reading-lamp',
'vi-ep:refresh',
'vi-ep:refresh-left',
'vi-ep:refresh-right',
'vi-ep:refrigerator',
'vi-ep:remove',
'vi-ep:remove-filled',
'vi-ep:right',
'vi-ep:scale-to-original',
'vi-ep:school',
'vi-ep:scissor',
'vi-ep:search',
'vi-ep:select',
'vi-ep:sell',
'vi-ep:semi-select',
'vi-ep:service',
'vi-ep:set-up',
'vi-ep:setting',
'vi-ep:share',
'vi-ep:ship',
'vi-ep:shop',
'vi-ep:shopping-bag',
'vi-ep:shopping-cart',
'vi-ep:shopping-cart-full',
'vi-ep:shopping-trolley',
'vi-ep:smoking',
'vi-ep:soccer',
'vi-ep:sold-out',
'vi-ep:sort',
'vi-ep:sort-down',
'vi-ep:sort-up',
'vi-ep:stamp',
'vi-ep:star',
'vi-ep:star-filled',
'vi-ep:stopwatch',
'vi-ep:success-filled',
'vi-ep:sugar',
'vi-ep:suitcase',
'vi-ep:suitcase-line',
'vi-ep:sunny',
'vi-ep:sunrise',
'vi-ep:sunset',
'vi-ep:switch',
'vi-ep:switch-button',
'vi-ep:switch-filled',
'vi-ep:takeaway-box',
'vi-ep:ticket',
'vi-ep:tickets',
'vi-ep:timer',
'vi-ep:toilet-paper',
'vi-ep:tools',
'vi-ep:top',
'vi-ep:top-left',
'vi-ep:top-right',
'vi-ep:trend-charts',
'vi-ep:trophy',
'vi-ep:trophy-base',
'vi-ep:turn-off',
'vi-ep:umbrella',
'vi-ep:unlock',
'vi-ep:upload',
'vi-ep:upload-filled',
'vi-ep:user',
'vi-ep:user-filled',
'vi-ep:van',
'vi-ep:video-camera',
'vi-ep:video-camera-filled',
'vi-ep:video-pause',
'vi-ep:video-play',
'vi-ep:view',
'vi-ep:wallet',
'vi-ep:wallet-filled',
'vi-ep:warn-triangle-filled',
'vi-ep:warning',
'vi-ep:warning-filled',
'vi-ep:watch',
'vi-ep:watermelon',
'vi-ep:wind-power',
'vi-ep:zoom-in',
'vi-ep:zoom-out'
]
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More