refactor: 更换 ESLint 配置为 @antfu/eslint-config

This commit is contained in:
2024-05-10 22:29:45 +08:00
parent 5101dd12d9
commit bfc8e42bad
148 changed files with 7314 additions and 5046 deletions

View File

@@ -1,7 +1,7 @@
import http from '@/utils/http'
import type * as Area from './type'
import http from '@/utils/http'
/** @desc 获取地区列表 */
export const getAreaList = (params: { type: 'province' | 'city' | 'area'; code?: string }) => {
export const getAreaList = (params: { type: 'province' | 'city' | 'area', code?: string }) => {
return http.get<Area.AreaItem>('/area/list', params)
}

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Auth from './type'
import http from '@/utils/http'
const BASE_URL = '/auth'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Common from './type'
import http from '@/utils/http'
const BASE_URL = '/captcha'

View File

@@ -1,6 +1,6 @@
import type { TreeNodeData } from '@arco-design/web-vue'
import http from '@/utils/http'
import type { LabelValueState } from '@/types/global'
import type { TreeNodeData } from '@arco-design/web-vue'
import type { OptionQuery } from '@/apis'
const BASE_URL = '/common'
@@ -16,7 +16,7 @@ export function listMenuTree(query: { description: string }) {
}
/** @desc 查询角色列表 */
export function listRoleDict(query?: { name: string; status: number }) {
export function listRoleDict(query?: { name: string, status: number }) {
return http.get<LabelValueState[]>(`${BASE_URL}/dict/role`, query)
}

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Common from './type'
import http from '@/utils/http'
const BASE_URL = '/dashboard'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Monitor from './type'
import http from '@/utils/http'
const BASE_URL = '/system/log'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Monitor from './type'
import http from '@/utils/http'
const BASE_URL = '/monitor/online'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/dept'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/dict'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/file'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/menu'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/notice'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/option'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/role'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/storage'

View File

@@ -261,7 +261,7 @@ export interface SecurityConfigResp {
password_update_interval: OptionResp
}
/** 绑定三方账号信息*/
/** 绑定三方账号信息 */
export interface BindSocialAccountRes {
source: string
description: string

View File

@@ -9,22 +9,22 @@ export function uploadAvatar(data: FormData) {
}
/** @desc 修改用户基本信息 */
export function updateUserBaseInfo(data: { nickname: string; gender: number }) {
export function updateUserBaseInfo(data: { nickname: string, gender: number }) {
return http.patch(`${BASE_URL}/basic/info`, data)
}
/** @desc 修改密码 */
export function updateUserPassword(data: { oldPassword: string; newPassword: string }) {
export function updateUserPassword(data: { oldPassword: string, newPassword: string }) {
return http.patch(`${BASE_URL}/password`, data)
}
/** @desc 修改手机号 */
export function updateUserPhone(data: { phone: string; captcha: string; oldPassword: string }) {
export function updateUserPhone(data: { phone: string, captcha: string, oldPassword: string }) {
return http.patch(`${BASE_URL}/phone`, data)
}
/** @desc 修改邮箱 */
export function updateUserEmail(data: { email: string; captcha: string; oldPassword: string }) {
export function updateUserEmail(data: { email: string, captcha: string, oldPassword: string }) {
return http.patch(`${BASE_URL}/email`, data)
}

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as System from './type'
import http from '@/utils/http'
const BASE_URL = '/system/user'

View File

@@ -1,5 +1,5 @@
import http from '@/utils/http'
import type * as Tool from './type'
import http from '@/utils/http'
const BASE_URL = '/generator'

View File

@@ -13,8 +13,8 @@
<script lang="ts" setup>
import type { RouteLocationMatched } from 'vue-router'
import { useRouteStore } from '@/stores'
import { findTree } from 'xe-utils'
import { useRouteStore } from '@/stores'
const route = useRoute()
const router = useRouter()

View File

@@ -11,12 +11,6 @@
<script lang="ts" setup>
defineOptions({ name: 'GiCellAvatar' })
interface Props {
avatar: string
name: string
isLink?: boolean
}
const props = withDefaults(defineProps<Props>(), {
avatar: '',
name: '',
@@ -26,6 +20,12 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
(e: 'click'): void
}>()
interface Props {
avatar: string
name: string
isLink?: boolean
}
</script>
<style lang="scss" scoped></style>

View File

@@ -15,13 +15,13 @@
<script lang="ts" setup>
defineOptions({ name: 'GiCellGender' })
interface Props {
gender: 1 | 2 | 0
}
const props = withDefaults(defineProps<Props>(), {
gender: 1
})
interface Props {
gender: 1 | 2 | 0
}
</script>
<style lang="scss" scoped></style>

View File

@@ -12,13 +12,13 @@
<script lang="ts" setup>
defineOptions({ name: 'GiCellStatus' })
interface Props {
status: 0 | 1
}
const props = withDefaults(defineProps<Props>(), {
status: 1
})
interface Props {
status: 0 | 1
}
</script>
<style lang="scss" scoped></style>

View File

@@ -21,13 +21,13 @@
<script lang="ts" setup>
defineOptions({ name: 'GiCellTags' })
interface Props {
data: string[]
}
withDefaults(defineProps<Props>(), {
data: () => []
})
interface Props {
data: string[]
}
</script>
<style lang="scss" scoped></style>

View File

@@ -17,6 +17,10 @@ import { githubLight } from '@ddietr/codemirror-themes/github-light'
import { oneDark } from '@codemirror/theme-one-dark'
import { useAppStore } from '@/stores'
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const appStore = useAppStore()
const isDark = computed(() => appStore.theme === 'dark')
@@ -24,11 +28,6 @@ interface Props {
type?: 'javascript' | 'vue'
codeJson: string
}
const props = withDefaults(defineProps<Props>(), {
type: 'javascript',
codeJson: ''
})
const defaultConfig = {
tabSize: 2,
basic: true,

View File

@@ -1,4 +1,4 @@
import { defineComponent, type PropType } from 'vue'
import { type PropType, defineComponent } from 'vue'
import './dot.scss'
type TPropsType = 'primary' | 'success' | 'warning' | 'danger' | 'info'

View File

@@ -9,22 +9,22 @@ import type { CSSProperties } from 'vue'
defineOptions({ name: 'GiFlexibleBox' })
interface Props {
modelValue: boolean
direction: 'left' | 'right'
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
direction: 'right'
})
interface Props {
modelValue: boolean
direction: 'left' | 'right'
}
const BoxRef = ref<HTMLElement | null>()
const style = computed(() => {
const obj: CSSProperties = {}
obj[`margin-${props.direction}`] =
!props.modelValue && BoxRef.value && BoxRef.value.clientWidth ? `-${BoxRef.value.clientWidth}px` : 0
obj[`margin-${props.direction}`]
= !props.modelValue && BoxRef.value && BoxRef.value.clientWidth ? `-${BoxRef.value.clientWidth}px` : 0
return obj
})
</script>

View File

@@ -5,9 +5,9 @@
<script lang="ts" setup>
import { useAppStore } from '@/stores'
const appStore = useAppStore()
defineOptions({ name: 'GiFooter' })
const appStore = useAppStore()
</script>
<style lang="scss" scoped>

View File

@@ -104,7 +104,7 @@
<template v-if="item.type === 'date-picker'">
<a-date-picker
:placeholder="`请选择日期`"
placeholder="请选择日期"
v-bind="(item.props as A.DatePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
@@ -113,7 +113,7 @@
<template v-if="item.type === 'time-picker'">
<a-time-picker
:placeholder="`请选择时间`"
placeholder="请选择时间"
v-bind="(item.props as A.TimePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
@@ -171,9 +171,9 @@
</template>
<script setup lang="ts">
import type { Options, Columns, ColumnsItemHide, ColumnsItemDisabled, ColumnsItem } from './type'
import type * as A from '@arco-design/web-vue'
import { cloneDeep } from 'lodash-es'
import type { Columns, ColumnsItem, ColumnsItemDisabled, ColumnsItemHide, Options } from './type'
interface Props {
modelValue: any

View File

@@ -1,7 +1,7 @@
import { reactive } from 'vue'
import { cloneDeep } from 'lodash-es'
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
import { Message } from '@arco-design/web-vue'
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
export function useGiForm(initValue: Columns) {
const getInitValue = () => cloneDeep(initValue)

View File

@@ -36,11 +36,11 @@ export type ColumnsItemRequest<F = any> = (form: F) => Promise<any>
export type ColumnsItemFormat<T = any> = (
res: T
) =>
| A.SelectInstance['$props']['options']
| A.RadioGroupInstance['$props']['options']
| A.CheckboxGroupInstance['$props']['options']
| A.CascaderInstance['$props']['options']
| A.TreeSelectInstance['$props']['data']
| A.SelectInstance['$props']['options']
| A.RadioGroupInstance['$props']['options']
| A.CheckboxGroupInstance['$props']['options']
| A.CascaderInstance['$props']['options']
| A.TreeSelectInstance['$props']['data']
export type ColumnsItemOptionsOrData =
| A.SelectInstance['$props']['options']
@@ -91,8 +91,8 @@ export interface Options {
form: Omit<A.FormInstance['$props'], 'model'>
row?: Partial<typeof import('@arco-design/web-vue')['Row']['__defaults']>
col?: A.ColProps
btns?: { hide?: boolean; span?: number; col?: A.ColProps; searchBtnText?: string }
fold?: { enable?: boolean; index?: number; defaultCollapsed?: boolean }
btns?: { hide?: boolean, span?: number, col?: A.ColProps, searchBtnText?: string }
fold?: { enable?: boolean, index?: number, defaultCollapsed?: boolean }
}
export type Columns<F = any> = ColumnsItem<F>[]

View File

@@ -55,7 +55,7 @@
<a-row justify="center" align="center">
<a-pagination
size="mini"
:pageSize="pageSize"
:page-size="pageSize"
:total="total"
:show-size-changer="false"
@change="onPageChange"
@@ -69,22 +69,24 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import { Message } from '@arco-design/web-vue'
// 自定义图标模块
const SvgIconModules = import.meta.glob('@/assets/icons/*.svg')
defineOptions({ name: 'GiIconSelector' })
const emit = defineEmits(['select', 'update:modelValue'])
interface Props {
modelValue?: string
enableCopy?: boolean
}
const props = withDefaults(defineProps<Props>(), {
modelValue: '',
enableCopy: false
})
const emit = defineEmits(['select', 'update:modelValue'])
// 自定义图标模块
const SvgIconModules = import.meta.glob('@/assets/icons/*.svg')
interface Props {
modelValue?: string
enableCopy?: boolean
}
const searchValue = ref('') // 搜索词
// 图标列表

View File

@@ -21,13 +21,6 @@
<script setup lang="ts">
defineOptions({ name: 'GiOptionItem' })
interface Props {
icon?: string
label?: string
more?: boolean
active?: boolean
}
const props = withDefaults(defineProps<Props>(), {
icon: '',
label: '',
@@ -39,6 +32,13 @@ const emit = defineEmits<{
(e: 'click'): void
}>()
interface Props {
icon?: string
label?: string
more?: boolean
active?: boolean
}
const handleClick = () => {
emit('click')
}

View File

@@ -21,12 +21,12 @@
<script lang="ts" setup>
defineOptions({ name: 'GiOverFlowTags' })
interface Props {
data: string[]
}
withDefaults(defineProps<Props>(), {
data: () => []
})
interface Props {
data: string[]
}
</script>
<style lang="scss" scoped></style>

View File

@@ -3,7 +3,7 @@
aria-hidden="true"
:class="svgClass"
v-bind="$attrs"
:style="{ color: color, fill: color, width: iconSize, height: iconSize }"
:style="{ color, fill: color, width: iconSize, height: iconSize }"
>
<use :xlink:href="iconName"></use>
</svg>
@@ -12,21 +12,21 @@
<script setup lang="ts">
defineOptions({ name: 'GiSvgIcon' })
interface Props {
name: string
color?: string
size?: string | number
}
const props = withDefaults(defineProps<Props>(), {
name: '',
color: '',
size: 20
})
interface Props {
name: string
color?: string
size?: string | number
}
// 判断传入的值是否带有单位如果没有就默认用px单位
const getUnitValue = (value: string | number): string | number => {
return /(px|em|rem|%)$/.test(value.toString()) ? value : value + 'px'
return /(px|em|rem|%)$/.test(value.toString()) ? value : `${value}px`
}
const iconSize = computed<string | number>(() => {

View File

@@ -1,5 +1,5 @@
<template>
<div ref="giTableRef" class="gi-table" :class="{ 'gi-table--fullscreen': isFullscreen }">
<div class="gi-table" :class="{ 'gi-table--fullscreen': isFullscreen }">
<a-row justify="space-between" align="center" class="gi-table__toolbar">
<a-space wrap class="gi-table__toolbar-left" :size="[8, 8]">
<slot name="custom-left"></slot>
@@ -18,9 +18,11 @@
</a-button>
</a-tooltip>
<template #content>
<a-doption v-for="item in sizeList" :key="item.value" :value="item.value" :active="item.value === size">{{
<a-doption v-for="item in sizeList" :key="item.value" :value="item.value" :active="item.value === size">
{{
item.label
}}</a-doption>
}}
</a-doption>
</template>
</a-dropdown>
<a-popover
@@ -38,7 +40,7 @@
</a-tooltip>
<template #content>
<div class="gi-table__draggable">
<VueDraggable ref="el" v-model="settingColumnList">
<VueDraggable v-model="settingColumnList">
<div v-for="item in settingColumnList" :key="item.title" class="drag-item">
<div class="drag-item__move"><icon-drag-dot-vertical /></div>
<a-checkbox v-model:model-value="item.show" :disabled="item.disabled">{{ item.title }}</a-checkbox>
@@ -73,17 +75,23 @@
v-bind="{ ...attrs, columns: _columns }"
>
<template v-for="key in Object.keys(slots)" :key="key" #[key]="scoped">
<slot :key="key" :name="key" v-bind="scoped"></slot> </template
></a-table>
<slot :key="key" :name="key" v-bind="scoped"></slot>
</template>
</a-table>
</div>
</div>
</template>
<script setup lang="ts">
import type { TableInstance, TableColumnData, DropdownInstance } from '@arco-design/web-vue'
import type { DropdownInstance, TableColumnData, TableInstance } from '@arco-design/web-vue'
import { VueDraggable } from 'vue-draggable-plus'
defineOptions({ name: 'GiTable', inheritAttrs: false })
const props = withDefaults(defineProps<Props>(), {
disabledTools: () => [], // 禁止显示的工具
disabledColumnKeys: () => [] // 禁止控制显示隐藏的列
})
const emit = defineEmits<{
(e: 'refresh'): void
}>()
@@ -96,18 +104,13 @@ interface Props {
disabledColumnKeys?: string[]
}
const props = withDefaults(defineProps<Props>(), {
disabledTools: () => [], // 禁止显示的工具
disabledColumnKeys: () => [] // 禁止控制显示隐藏的列
})
const tableRef = ref<TableInstance | null>(null)
const stripe = ref(false)
const size = ref<TableInstance['size']>('medium')
const isBordered = ref(false)
const isFullscreen = ref(false)
type SizeItem = { label: string; value: TableInstance['size'] }
type SizeItem = { label: string, value: TableInstance['size'] }
const sizeList: SizeItem[] = [
{ label: '紧凑', value: 'small' },
{ label: '默认', value: 'medium' }
@@ -127,7 +130,7 @@ const showFullscreenBtn = computed(() => !props.disabledTools.includes('fullscre
const showSettingColumnBtn = computed(
() => !props.disabledTools.includes('setting') && attrs?.columns && (attrs?.columns as TableColumnData[])?.length
)
type SettingColumnItem = { title: string; key: string; show: boolean; disabled: boolean }
type SettingColumnItem = { title: string, key: string, show: boolean, disabled: boolean }
const settingColumnList = ref<SettingColumnItem[]>([])
// 重置配置列

View File

@@ -1,4 +1,4 @@
import { defineComponent, computed, type PropType } from 'vue'
import { type PropType, computed, defineComponent } from 'vue'
import './tag.scss'
type TPropsType = 'dark' | 'light' | 'outline' | 'light-outline'

View File

@@ -1,6 +1,6 @@
<template>
<div class="json_pretty_container">
<VueJsonPretty :path="'res'" :data="JSONObject" :show-length="true" />
<VueJsonPretty path="res" :data="JSONObject" :show-length="true" />
<icon-copy class="copy_icon" @click="onCopy(JSONObject)" />
</div>
</template>

View File

@@ -6,6 +6,7 @@
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
interface Props {
value: any
}

View File

@@ -1,4 +1,4 @@
type LabelValueItem = { label: string; value: number; color: string }
type LabelValueItem = { label: string, value: number, color: string }
export const DisEnableStatusList: LabelValueItem[] = [
{ label: '启用', value: 1, color: 'green' },
{ label: '禁用', value: 2, color: 'red' }

View File

@@ -1,4 +1,4 @@
import type { DirectiveBinding, Directive } from 'vue'
import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/stores'
/**

View File

@@ -1,4 +1,4 @@
import type { DirectiveBinding, Directive } from 'vue'
import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/stores'
/**

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { listDeptTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue'
import { listDeptTree } from '@/apis'
/** 部门模块 */
export function useDept(options?: { onSuccess?: () => void }) {

View File

@@ -1,6 +1,6 @@
import { reactive, computed, ref, type Ref } from 'vue'
import { type Ref, computed, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Modal, Message, type FormInstance } from '@arco-design/web-vue'
import { type FormInstance, Message, Modal } from '@arco-design/web-vue'
import { isEqual } from 'lodash-es'
type Option<T> = {

View File

@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { listMenuTree } from '@/apis'
import type { TreeNodeData } from '@arco-design/web-vue'
import { listMenuTree } from '@/apis'
/** 菜单模块 */
export function useMenu(options?: { onSuccess?: () => void }) {

View File

@@ -1,4 +1,4 @@
import { computed, type ComputedRef } from 'vue'
import { type ComputedRef, computed } from 'vue'
import { useBreakpoints } from '@vueuse/core'
import type { ColProps } from '@arco-design/web-vue'

View File

@@ -16,7 +16,7 @@ export function useBreakpointIndex(callback: (v: number) => void, breakpointObj?
() => breakpoint.value,
(v) => {
const def = { xs: 0, sm: 0, md: 0, lg: 1, xl: 1, xxl: 2 }
const obj = breakpointObj ? breakpointObj : def
const obj = breakpointObj || def
callback(obj[v as keyof typeof obj])
},
{ immediate: true }

View File

@@ -2,13 +2,13 @@ import { Message, Notification } from '@arco-design/web-vue'
/**
* @description 接收数据流生成 blob创建链接下载文件
* @param {Function} api 导出表格的api方法 (必传)
* @param {String} tempName 导出的文件名 (必传)
* @param {Object} params 导出的参数 (默认{})
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
* */
* @param {string} tempName 导出的文件名 (必传)
* @param {object} params 导出的参数 (默认{})
* @param {boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {string} fileType 导出的文件格式 (默认为.xlsx)
*/
interface NavigatorWithMsSaveOrOpenBlob extends Navigator {
msSaveOrOpenBlob(blob: Blob, fileName: string): void
msSaveOrOpenBlob: (blob: Blob, fileName: string) => void
}
export const useDownload = async (api: () => Promise<any>, isNotify = true, tempName = '', fileType = '.xlsx') => {
try {
@@ -16,7 +16,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
if (res.headers['content-disposition']) {
tempName = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
} else {
tempName = tempName ? tempName : new Date().getTime() + fileType
tempName = tempName || new Date().getTime() + fileType
}
if (isNotify && !res?.code) {
Notification.warning({
@@ -24,7 +24,7 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
content: '如果数据庞大会导致下载缓慢哦,请您耐心等待!'
})
}
if (res.status !== 200 || res.data === null || !(res.data instanceof Blob)) {
if (res.status !== 200 || res.data == null || !(res.data instanceof Blob)) {
Message.error('导出失败,请稍后再试!')
return
}
@@ -44,6 +44,6 @@ export const useDownload = async (api: () => Promise<any>, isNotify = true, temp
document.body.removeChild(exportFile)
window.URL.revokeObjectURL(blobUrl)
} catch (error) {
console.log(error)
// console.log(error)
}
}

View File

@@ -1,4 +1,4 @@
import { ref, type UnwrapRef } from 'vue'
import { type UnwrapRef, ref } from 'vue'
import type { AxiosResponse } from 'axios'
import { useLoading } from '@/hooks'

View File

@@ -1,5 +1,5 @@
import type { TableInstance, TableData } from '@arco-design/web-vue'
import { Modal, Message } from '@arco-design/web-vue'
import type { TableData, TableInstance } from '@arco-design/web-vue'
import { Message, Modal } from '@arco-design/web-vue'
import { usePagination } from '@/hooks'
interface Options<T> {
@@ -9,11 +9,12 @@ interface Options<T> {
rowKey?: keyof T
}
type PaginationParams = { page: number; size: number }
type PaginationParams = { page: number, size: number }
type Api<T> = (params: PaginationParams) => Promise<ApiRes<PageRes<T[]>>>
export function useTable<T>(api: Api<T>, options?: Options<T>) {
const { formatResult, onSuccess, immediate, rowKey } = options || {}
// eslint-disable-next-line ts/no-use-before-define
const { pagination, setTotal } = usePagination(() => getTableData())
const loading = ref(false)
const tableData = ref<T[]>([])
@@ -34,12 +35,6 @@ export function useTable<T>(api: Api<T>, options?: Options<T>) {
const isImmediate = immediate ?? true
isImmediate && getTableData()
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 多选
const selectedKeys = ref<(string | number)[]>([])
const select: TableInstance['onSelect'] = (rowKeys) => {
@@ -53,10 +48,16 @@ export function useTable<T>(api: Api<T>, options?: Options<T>) {
selectedKeys.value = checked ? arr.map((i) => i[key as string]) : []
}
// 查询
const search = () => {
selectedKeys.value = []
pagination.onChange(1)
}
// 删除
const handleDelete = async <T>(
deleteApi: () => Promise<ApiRes<T>>,
options?: { title?: string; content?: string; successTip?: string; showModal?: boolean }
options?: { title?: string, content?: string, successTip?: string, showModal?: boolean }
): Promise<boolean | undefined> => {
const onDelete = async () => {
try {

View File

@@ -38,6 +38,8 @@
</template>
<script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router'
import { searchTree } from 'xe-utils'
import Main from './components/Main.vue'
import Tabs from './components/Tabs/index.vue'
import Menu from './components/Menu/index.vue'
@@ -45,9 +47,7 @@ import HeaderRightBar from './components/HeaderRightBar/index.vue'
import Logo from './components/Logo.vue'
import MenuFoldBtn from './components/MenuFoldBtn.vue'
import { useAppStore, useRouteStore } from '@/stores'
import type { RouteRecordRaw } from 'vue-router'
import { isExternal } from '@/utils/validate'
import { searchTree } from 'xe-utils'
import { filterTree } from '@/utils'
import { useDevice } from '@/hooks'
@@ -63,23 +63,11 @@ const menuRoutes = filterTree(routeStore.routes, (i) => i.meta?.hidden === false
// 顶部一级菜单
const topMenus = ref<RouteRecordRaw[]>([])
topMenus.value = JSON.parse(JSON.stringify(menuRoutes))
console.log('topMenus', toRaw(topMenus.value))
const getMenuIcon = (item: RouteRecordRaw) => {
return item.meta?.icon || item.children?.[0].meta?.icon
}
const onMenuItemClick = (key: string) => {
if (isExternal(key)) {
window.open(key)
return
}
setTimeout(() => getLeftMenus(key))
const obj = topMenus.value.find((i) => i.path === key)
if (obj && obj.redirect === 'noRedirect') return
router.push({ path: key })
}
// 克隆是菜单的路由
const cloneMenuRoutes: RouteRecordRaw[] = JSON.parse(JSON.stringify(menuRoutes))
// 顶部一级菜单选中的
@@ -95,6 +83,17 @@ const getLeftMenus = (key: string) => {
leftMenus.value = obj ? (obj.children as RouteRecordRaw[]) : []
}
const onMenuItemClick = (key: string) => {
if (isExternal(key)) {
window.open(key)
return
}
setTimeout(() => getLeftMenus(key))
const obj = topMenus.value.find((i) => i.path === key)
if (obj && obj.redirect === 'noRedirect') return
router.push({ path: key })
}
watch(
() => route.path,
(newPath) => {

View File

@@ -23,9 +23,9 @@
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores'
import Menu from '../Menu/index.vue'
import Logo from '../Logo.vue'
import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks'
defineOptions({ name: 'Asider' })

View File

@@ -82,10 +82,10 @@
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores'
import { ColorPicker } from 'vue-color-kit'
import 'vue-color-kit/dist/vue-color-kit.css'
import LayoutItem from './components/LayoutItem.vue'
import { useAppStore } from '@/stores'
defineOptions({ name: 'SettingDrawer' })
const appStore = useAppStore()
@@ -133,8 +133,8 @@ const defaultColorList = [
type ColorObj = {
hex: string
hsv: { h: number; s: number; v: number }
rgba: { r: number; g: number; b: number; a: number }
hsv: { h: number, s: number, v: number }
rgba: { r: number, g: number, b: number, a: number }
}
// 改变主题色

View File

@@ -67,15 +67,16 @@
<script setup lang="ts">
import { Modal } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import { useFullscreen } from '@vueuse/core'
import SettingDrawer from './SettingDrawer.vue'
import Message from './Message.vue'
import { useUserStore } from '@/stores'
import { isMobile } from '@/utils'
import { useFullscreen } from '@vueuse/core'
defineOptions({ name: 'HeaderRight' })
const { isFullscreen, toggle } = useFullscreen()
defineOptions({ name: 'HeaderRight' })
const router = useRouter()
const userStore = useUserStore()
const SettingDrawerRef = ref<InstanceType<typeof SettingDrawer>>()

View File

@@ -9,6 +9,9 @@
<script setup lang="ts">
import { useAppStore } from '@/stores'
const props = withDefaults(defineProps<Props>(), {
collapsed: false
})
const appStore = useAppStore()
const title = computed(() => appStore.getTitle())
const logo = computed(() => appStore.getLogo())
@@ -16,10 +19,6 @@ const logo = computed(() => appStore.getLogo())
interface Props {
collapsed?: boolean
}
const props = withDefaults(defineProps<Props>(), {
collapsed: false
})
const router = useRouter()
// 跳转首页
const toHome = () => {

View File

@@ -2,9 +2,9 @@
<template v-if="!item.meta?.hidden">
<a-menu-item
v-if="
isOneShowingChild &&
(!onlyOneChild?.children || onlyOneChild?.meta?.noShowingChildren) &&
!item?.meta?.alwaysShow
isOneShowingChild
&& (!onlyOneChild?.children || onlyOneChild?.meta?.noShowingChildren)
&& !item?.meta?.alwaysShow
"
v-bind="attrs"
:key="onlyOneChild?.path"
@@ -32,14 +32,14 @@ import type { RouteRecordRaw } from 'vue-router'
import MenuIcon from './MenuIcon.vue'
defineOptions({ name: 'MenuItem' })
const props = withDefaults(defineProps<Props>(), {})
const attrs = useAttrs()
interface Props {
item: RouteRecordRaw
}
const props = withDefaults(defineProps<Props>(), {})
// 如果hidden: false那么代表这个路由项显示在左侧菜单栏中
// 如果props.item的子项chidren只有一个hidden: false的子元素, 那么onlyOneChild就表示这个子元素
const onlyOneChild = ref<RouteRecordRaw | null>(null)

View File

@@ -11,21 +11,23 @@
@menu-item-click="onMenuItemClick"
@collapse="onCollapse"
>
<MenuItem v-for="(route, index) in sidebarRoutes" :key="route.path + index" :item="route"></MenuItem>
<MenuItem v-for="(item, index) in sidebarRoutes" :key="item.path + index" :item="item"></MenuItem>
</a-menu>
</template>
<script setup lang="ts">
import { useAppStore, useRouteStore } from '@/stores'
import MenuItem from './MenuItem.vue'
import { isExternal } from '@/utils/validate'
import type { RouteRecordRaw } from 'vue-router'
import type { CSSProperties } from 'vue'
import MenuItem from './MenuItem.vue'
import { useAppStore, useRouteStore } from '@/stores'
import { isExternal } from '@/utils/validate'
import { useDevice } from '@/hooks'
defineOptions({ name: 'Menu' })
defineOptions({ name: 'AppMenu' })
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{
(e: 'menuItemClickAfter'): void
(e: 'menu-item-click-after'): void
}>()
interface Props {
@@ -33,15 +35,12 @@ interface Props {
menuStyle?: CSSProperties
}
const props = withDefaults(defineProps<Props>(), {})
const { isDesktop } = useDevice()
const route = useRoute()
const router = useRouter()
const appStore = useAppStore()
const routeStore = useRouteStore()
const sidebarRoutes = computed(() => (props.menus ? props.menus : routeStore.routes))
// console.log('sidebarRoutes', sidebarRoutes.value)
// 菜单垂直模式/水平模式
const mode = computed(() => {
@@ -73,7 +72,7 @@ const onMenuItemClick = (key: string) => {
return
}
router.push({ path: key })
emit('menuItemClickAfter')
emit('menu-item-click-after')
}
// 折叠状态改变时触发

View File

@@ -20,7 +20,7 @@
:drawer-style="{
'border-right': '1px solid var(--color-border-2)',
'box-sizing': 'border-box',
'background-color': 'var(--color-bg-1)'
'background-color': 'var(--color-bg-1)',
}"
>
<Logo :collapsed="false"></Logo>

View File

@@ -41,8 +41,8 @@
<script setup lang="ts">
import type { RouteRecordRaw } from 'vue-router'
import { useTabsStore, useAppStore } from '@/stores'
import MagicIcon from './MagicIcon.vue'
import { useAppStore, useTabsStore } from '@/stores'
defineOptions({ name: 'Tabs' })
const route = useRoute()
@@ -53,14 +53,6 @@ const tabsStore = useTabsStore()
// 重置, 同时把 affix: true 的路由筛选出来
tabsStore.reset()
// 监听路由变化
watch(
() => route.path,
() => {
handleRouteChange()
}
)
// 路由发生改变触发
const handleRouteChange = () => {
const item = { ...route } as unknown as RouteRecordRaw
@@ -72,6 +64,14 @@ const handleRouteChange = () => {
}
handleRouteChange()
// 监听路由变化
watch(
() => route.path,
() => {
handleRouteChange()
}
)
// 点击页签
const handleTabClick = (key: string) => {
router.push({ path: key })

View File

@@ -1,22 +1,14 @@
import { createApp } from 'vue'
import pinia from '@/stores'
import App from './App.vue'
import router from './router'
// 引入 Arco Design 组件库以及自定义主题
import ArcoVue from '@arco-design/web-vue'
import ArcoVue, { Card, Modal } from '@arco-design/web-vue'
import '@/styles/arco-ui/index.less'
import 'md-editor-v3/lib/style.css'
// import '@arco-themes/vue-gi-demo/index.less'
// import '@arco-design/web-vue/dist/arco.css'
// 对特定组件进行默认配置
import { Card, Modal } from '@arco-design/web-vue'
Card.props.bordered = false
// 额外引入 Arco Design Icon图标库
import ArcoVueIcon from '@arco-design/web-vue/es/icon'
import App from './App.vue'
import router from './router'
import '@/router/permission'
@@ -34,6 +26,10 @@ import 'virtual:svg-icons-register'
// 自定义指令
import directives from './directives'
import pinia from '@/stores'
// 对特定组件进行默认配置
Card.props.bordered = false
const app = createApp(App)
Modal._context = app._context

View File

@@ -1,2 +1,2 @@
/** 省市区数据类型 */
export type MockAreaItem = { label: string; code: string; children?: MockAreaItem[] }
export type MockAreaItem = { label: string, code: string, children?: MockAreaItem[] }

View File

@@ -1,6 +1,6 @@
import { defineMock } from '../_base'
import { resultSuccess, getDelayTime } from '../_utils'
import { findTree } from 'xe-utils'
import { defineMock } from '../_base'
import { getDelayTime, resultSuccess } from '../_utils'
import areaData from '../_data/area'
export default defineMock([

View File

@@ -1,4 +1,4 @@
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'
import { type RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
/** 默认布局 */
const Layout = () => import('@/layout/index.vue')

View File

@@ -1,5 +1,5 @@
import router from '@/router'
import { useUserStore, useRouteStore } from '@/stores'
import { useRouteStore, useUserStore } from '@/stores'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
@@ -46,7 +46,7 @@ router.beforeEach(async (to, from, next) => {
}
} else {
// 如果没有 Token
if (whiteList.indexOf(to.path) !== -1) {
if (whiteList.includes(to.path)) {
// 如果在免登录的白名单中,则直接进入
next()
} else {

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import { listOptionDict, type BasicConfigResp } from '@/apis'
import { computed, reactive, toRefs } from 'vue'
import { generate, getRgbStr } from '@arco-design/color'
import { type BasicConfigResp, listOptionDict } from '@/apis'
import defaultSettings from '@/config/setting.json'
const storeSetup = () => {
@@ -20,6 +20,17 @@ const storeSetup = () => {
return obj
})
// 设置主题色
const setThemeColor = (color: string) => {
if (!color) return
settingConfig.themeColor = color
const list = generate(settingConfig.themeColor, { list: true, dark: settingConfig.theme === 'dark' })
list.forEach((color: string, index: number) => {
const rgbStr = getRgbStr(color)
document.body.style.setProperty(`--primary-${index + 1}`, rgbStr)
})
}
// 切换主题 暗黑模式|简白模式
const toggleTheme = (dark: boolean) => {
if (dark) {
@@ -32,17 +43,6 @@ const storeSetup = () => {
setThemeColor(settingConfig.themeColor)
}
// 设置主题色
const setThemeColor = (color: string) => {
if (!color) return
settingConfig.themeColor = color
const list = generate(settingConfig.themeColor, { list: true, dark: settingConfig.theme === 'dark' })
list.forEach((color: string, index: number) => {
const rgbStr = getRgbStr(color)
document.body.style.setProperty(`--primary-${index + 1}`, rgbStr)
})
}
// 初始化主题
const initTheme = () => {
if (!settingConfig.themeColor) return

View File

@@ -1,11 +1,11 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router'
import { constantRoutes } from '@/router'
import ParentView from '@/components/ParentView/index.vue'
import { getUserRoute, type RouteItem } from '@/apis'
import { mapTree, toTreeArray } from 'xe-utils'
import { cloneDeep, omit } from 'lodash-es'
import { constantRoutes } from '@/router'
import ParentView from '@/components/ParentView/index.vue'
import { type RouteItem, getUserRoute } from '@/apis'
import { transformPathToName } from '@/utils'
const Layout = () => import('@/layout/index.vue')
@@ -58,7 +58,7 @@ const formatAsyncRoutes = (menus: RouteItem[]) => {
title: item.title,
hidden: item.isHidden,
keepAlive: item.isCache,
alwaysShow: item.type == 1,
alwaysShow: item.type === 1,
icon: item.icon
}
}

View File

@@ -1,9 +1,9 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import router from '@/router'
import type { RouteRecordRaw, RouteRecordName } from 'vue-router'
import { useRouteStore } from '@/stores'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import _XEUtils_ from 'xe-utils'
import router from '@/router'
import { useRouteStore } from '@/stores'
const storeSetup = () => {
const tagList = ref<RouteRecordRaw[]>([]) // 保存页签tab的数组

View File

@@ -1,19 +1,19 @@
import { defineStore } from 'pinia'
import { ref, reactive, computed } from 'vue'
import { computed, reactive, ref } from 'vue'
import { resetRouter } from '@/router'
import {
accountLogin as accountLoginApi,
phoneLogin as phoneLoginApi,
emailLogin as emailLoginApi,
socialLogin as socialLoginApi,
logout as logoutApi,
getUserInfo as getUserInfoApi,
type AccountLoginReq,
type PhoneLoginReq,
type EmailLoginReq,
type UserInfo
type PhoneLoginReq,
type UserInfo,
accountLogin as accountLoginApi,
emailLogin as emailLoginApi,
getUserInfo as getUserInfoApi,
logout as logoutApi,
phoneLogin as phoneLoginApi,
socialLogin as socialLoginApi
} from '@/apis'
import { setToken, clearToken, getToken } from '@/utils/auth'
import { clearToken, getToken, setToken } from '@/utils/auth'
import { resetHasRouteFlag } from '@/router/permission'
import getAvatar from '@/utils/avatar'
@@ -76,6 +76,15 @@ const storeSetup = () => {
token.value = res.data.token
}
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
permissions.value = []
pwdExpiredShow.value = true
resetToken()
resetRouter()
}
// 退出登录
const logout = async () => {
try {
@@ -87,15 +96,6 @@ const storeSetup = () => {
}
}
// 退出登录回调
const logoutCallBack = async () => {
roles.value = []
permissions.value = []
pwdExpiredShow.value = true
resetToken()
resetRouter()
}
// 获取用户信息
const getInfo = async () => {
const res = await getUserInfoApi()

View File

@@ -6,7 +6,7 @@ declare module 'vue-router' {
title?: string
/** 设置该路由的图标, 记得将svg导入 @/icons/svg */
svgIcon?: string
/** 设置该路由的图标, 直接使用Arco Design的Icon(与svgIcon同时设置时, svgIcon将优先生效)*/
/** 设置该路由的图标, 直接使用Arco Design的Icon(与svgIcon同时设置时, svgIcon将优先生效) */
icon?: string
/** 默认false, 设置true的时候该路由不会在侧边栏出现 */
hidden?: boolean

View File

@@ -25,7 +25,7 @@ export function downloadByUrl({
isSameHost: boolean
}): Promise<boolean> {
// 是否同源
const isSameHost = new URL(url).host == location.host
const isSameHost = new URL(url).host === location.host
return new Promise<boolean>((resolve) => {
if (isSameHost) {
const link = document.createElement('a')
@@ -43,7 +43,7 @@ export function downloadByUrl({
return resolve(true)
}
if (url.indexOf('?') === -1) {
if (!url.includes('?')) {
url += '?download'
}

View File

@@ -15,9 +15,9 @@ export function encryptByMd5(txt: string) {
return md5(txt).toString()
}
const publicKey =
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u' +
'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
const publicKey
= 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u'
+ 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
export function encryptByRsa(txt: string) {
const encryptor = new JSEncrypt()

View File

@@ -1,12 +1,12 @@
import axios from 'axios'
import qs from 'query-string'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import NProgress from 'nprogress'
import { useUserStore } from '@/stores'
import { getToken } from '@/utils/auth'
import modalErrorWrapper from '@/utils/modal-error-wrapper'
import messageErrorWrapper from '@/utils/message-error-wrapper'
import notificationErrorWrapper from '@/utils/notification-error-wrapper'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import router from '@/router'
@@ -103,11 +103,11 @@ http.interceptors.response.use(
(error) => {
NProgress.done()
const response = Object.assign({}, error.response)
response &&
messageErrorWrapper({
content: StatusCodeMessage[response.status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员',
duration: 5 * 1000
})
response
&& messageErrorWrapper({
content: StatusCodeMessage[response.status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员',
duration: 5 * 1000
})
return Promise.reject(error)
}
)

View File

@@ -1,7 +1,7 @@
import { isExternal } from '@/utils/validate'
import { browse, mapTree } from 'xe-utils'
import { upperFirst, camelCase } from 'lodash-es'
import { camelCase, upperFirst } from 'lodash-es'
import { Message } from '@arco-design/web-vue'
import { isExternal } from '@/utils/validate'
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
@@ -14,16 +14,17 @@ export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
* pos="both": 去除两边空格
* pos="left": 去除左边空格
* pos="right": 去除右边空格
* pos="all": 去除所有空格 */
* pos="all": 去除所有空格
*/
type Pos = 'both' | 'left' | 'right' | 'all'
export function trim(str: string, pos: Pos = 'both'): string {
if (pos == 'both') {
if (pos === 'both') {
return str.replace(/^\s+|\s+$/g, '')
} else if (pos == 'left') {
} else if (pos === 'left') {
return str.replace(/^\s*/, '')
} else if (pos == 'right') {
} else if (pos === 'right') {
return str.replace(/(\s*$)/g, '')
} else if (pos == 'all') {
} else if (pos === 'all') {
return str.replace(/\s+/g, '')
} else {
return str
@@ -32,7 +33,8 @@ export function trim(str: string, pos: Pos = 'both'): string {
/**
* 根据数字获取对应的汉字
* @param {number} num - 数字(0-10) */
* @param {number} num - 数字(0-10)
*/
export function getHanByNumber(num: number): string {
const str = '零一二三四五六七八九十'
return str.charAt(num)
@@ -41,7 +43,8 @@ export function getHanByNumber(num: number): string {
/**
* 获取指定整数范围内的随机整数
* @param {number} start - 开始范围
* @param {number} end - 结束范围 */
* @param {number} end - 结束范围
*/
export function getRandomInterger(start = 0, end: number): number {
const range = end - start
return Math.floor(Math.random() * range + start)
@@ -59,30 +62,31 @@ export function getTypeOf(value: any) {
/**
* @desc 格式化电话号码
* @demo 183-7983-6654 */
@demo 183-7983-6654 */
export function formatPhone(mobile: string, formatStr = '-') {
return mobile.replace(/(?=(\d{4})+$)/g, formatStr)
}
/**
* @desc 手机号脱敏
* @demo 155****8810 */
@demo 155****8810 */
export function hidePhone(phone: string) {
return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
}
/** @desc 检测数据是否为空数据 */
export function isEmpty(data: unknown) {
if (data === '' || data === 'undefined' || data === undefined || data === null || data === 'null') {
if (data === '' || data === 'undefined' || data === undefined || data == null || data === 'null') {
return true
}
return JSON.stringify(data) == '{}' || JSON.stringify(data) == '[]' || JSON.stringify(data) == '[{}]'
return JSON.stringify(data) === '{}' || JSON.stringify(data) === '[]' || JSON.stringify(data) === '[{}]'
}
/**
* @desc 大小写转换
* @param {string} str 待转换的字符串
* @param {number} type 1:全大写 2:全小写 3:首字母大写 */
* @param {number} type 1:全大写 2:全小写 3:首字母大写
*/
export function toCase(str: string, type: number) {
switch (type) {
case 1:
@@ -100,39 +104,39 @@ export function toCase(str: string, type: number) {
* @desc 获取随机数
* @param {number} min 最小值
* @param {number} max 最大值
* */
*/
export const randomNum = (min: number, max: number) => {
return Math.floor(min + Math.random() * (max + 1 - min))
}
/**
* @desc 获取最大值 */
@desc 获取最大值 */
export const max = (arr: number[]) => {
return Math.max.apply(null, arr)
}
/**
* @desc 获取最小值 */
@desc 获取最小值 */
export const min = (arr: number[]) => {
return Math.min.apply(null, arr)
}
/**
* @desc 求和 */
@desc 求和 */
export const sum = (arr: number[]) => {
return arr.reduce((pre, cur) => pre + cur)
}
/**
* @desc 获取平均值 */
@desc 获取平均值 */
export const average = (arr: number[]) => {
return sum(arr) / arr.length
}
/**
* @desc 深拷贝 */
@desc 深拷贝 */
export const deepClone = (data: any) => {
if (typeof data !== 'object' || data === null) return '不是对象'
if (typeof data !== 'object' || data == null) return '不是对象'
const newData: any = Array.isArray(data) ? [] : {}
for (const key in data) {
newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key]
@@ -142,35 +146,38 @@ export const deepClone = (data: any) => {
/**
* @desc 判断是否是闰年
* @param {number} year 年份 */
* @param {number} year 年份
*/
export const isLeapYear = (year: number) => {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
/**
* @desc 判断是否是奇数
* @param {number} num 数字 */
* @param {number} num 数字
*/
export const isOdd = (num: number) => {
return num % 2 !== 0
}
/**
* @desc 判断是否是偶数
* @param {number} num 数字 */
* @param {number} num 数字
*/
export const isEven = (num: number) => {
return !isOdd(num)
}
/**
* @desc 将RGB转化为十六机制 */
@desc 将RGB转化为十六机制 */
export const rgbToHex = (r: number, g: number, b: number) => {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
}
/**
* @desc 获取随机十六进制颜色 */
@desc 获取随机十六进制颜色 */
export const randomHex = () => {
return `#${Math.floor(Math.random() * 0xffffff)
return `#${Math.floor(Math.random() * 0xFFFFFF)
.toString(16)
.padEnd(6, '0')}`
}
@@ -206,7 +213,7 @@ export const filterTree: FilterTree = (values, fn) => {
return data
}
type SortTree = <T extends { sort: number; children?: T[] }>(array: T[]) => T[]
type SortTree = <T extends { sort: number, children?: T[] }>(array: T[]) => T[]
/**
* @desc 排序树
* @param values /
@@ -238,7 +245,7 @@ export const formatFileSize = (fileSize: number) => {
}
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
let index = 0
const srcSize = parseFloat(fileSize.toString())
const srcSize = Number.parseFloat(fileSize.toString())
index = Math.floor(Math.log(srcSize) / Math.log(1024))
const size = srcSize / 1024 ** index
return `${size.toFixed(2)} ${unitArr[index]}`

View File

@@ -18,14 +18,14 @@ export const Code_6 = /^\d{6}$/
export const Code_4 = /^\d{4}$/
/** @desc 正则-url链接 */
export const Url =
/(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/
export const Url
= /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/
/** @desc 正则-16进颜色值 #333 #8c8c8c */
export const ColorRegex = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
/** @desc 正则-只能是中文 */
export const OnlyCh = /^[\u4e00-\u9fa5]+$/gi
export const OnlyCh = /^[\u4E00-\u9FA5]+$/gi
/** @desc 正则-只能是英文 */
export const OnlyEn = /^[a-zA-Z]*$/

View File

@@ -6,5 +6,5 @@ export const isExternal = (path: string) => {
/** 判断 url 是否是 http 或 https */
export function isHttp(url: string) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
return url.includes('http://') || url.includes('https://')
}

View File

@@ -23,13 +23,12 @@ import Icon500 from '@/components/icons/Icon500.vue'
defineOptions({ name: 'ErrorPage' })
interface Props {
code: number
}
const props = withDefaults(defineProps<Props>(), {
code: 403
})
interface Props {
code: number
}
const IconMap: Record<number, Component> = {
403: Icon403,
404: Icon404,

View File

@@ -9,5 +9,5 @@ const router = useRouter()
const { params, query } = route
const { path } = params
router.replace({ path: '/' + path, query })
router.replace({ path: `/${path}`, query })
</script>

View File

@@ -13,11 +13,28 @@
</template>
<script lang="ts" setup>
import { listDashboardAccessTrend, type DashboardAccessTrendResp } from '@/apis'
import VCharts from 'vue-echarts'
import { graphic } from 'echarts'
import { type DashboardAccessTrendResp, listDashboardAccessTrend } from '@/apis'
import { useChart } from '@/hooks'
// 提示框
const tooltipItemsHtmlString = (items) => {
return items
.map(
(el) => `<div class="content-panel">
<p>
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
<span>${el.seriesName}</span>
</p>
<span class="tooltip-value">
${el.value}
</span>
</div>`
)
.join('')
}
const xData = ref<string[]>([])
const pvStatisticsData = ref<number[]>([])
const ipStatisticsData = ref<number[]>([])
@@ -196,23 +213,6 @@ const onChange = (days: number) => {
getChartData(days)
}
// 提示框
const tooltipItemsHtmlString = (items) => {
return items
.map(
(el) => `<div class="content-panel">
<p>
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
<span>${el.seriesName}</span>
</p>
<span class="tooltip-value">
${el.value}
</span>
</div>`
)
.join('')
}
onMounted(() => {
getChartData(30)
})

View File

@@ -2,7 +2,7 @@
<a-card title="快捷操作" :bordered="false" size="medium" class="card gi_card_title">
<a-card-grid v-for="(item, index) in list" :key="item.name" class="card-grid-item" :style="{ width: '33.33%' }">
<a-card :bordered="false" hoverable>
<a-row justify="center" align="center" :class="'animated-fade-up-' + (index + 1)">
<a-row justify="center" align="center" :class="`animated-fade-up-${index + 1}`">
<a-space direction="vertical" align="center" class="wrapper" @click="router.replace({ path: item.path })">
<component :is="item.icon" :size="30" class="icon"></component>
<a-typography-text class="text">{{ item.name }}</a-typography-text>

View File

@@ -7,7 +7,7 @@
v-for="(item, index) in list"
:key="index"
align="right"
:class="'animated-fade-up-' + index"
:class="`animated-fade-up-${index}`"
style="overflow: hidden"
>
<template #content>

View File

@@ -8,7 +8,7 @@
v-for="(item, index) in dataList"
:key="index"
align="right"
:class="'animated-fade-up-' + index"
:class="`animated-fade-up-${index}`"
style="overflow: hidden"
>
<template #content>
@@ -26,7 +26,7 @@
</template>
<script setup lang="ts">
import { listDashboardNotice, type DashboardNoticeResp } from '@/apis'
import { type DashboardNoticeResp, listDashboardNotice } from '@/apis'
import { useDict } from '@/hooks/app'
import NoticeDetailModal from '@/views/system/notice/NoticeDetailModal.vue'

View File

@@ -3,7 +3,7 @@
<a-row align="stretch">
<a-col v-for="(item, index) in list" :key="item.name" :xs="12" :sm="8" :md="8">
<a-card-grid class="w-full h-full">
<a-card :bordered="false" hoverable :class="'animated-fade-up-' + index">
<a-card :bordered="false" hoverable :class="`animated-fade-up-${index}`">
<a :href="item.url" target="_blank">
<section class="item">
<div class="item__header">

View File

@@ -26,10 +26,10 @@
<script setup lang="ts">
import NowTime from './NowTime/index.vue'
import SupportCard from './SupportCard.vue'
import { useDevice } from '@/hooks'
import { useUserStore } from '@/stores'
import { goodTimeText } from '@/utils'
import SupportCard from './SupportCard.vue'
const { isDesktop } = useDevice()
const userStore = useUserStore()

View File

@@ -31,19 +31,17 @@
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
>立即登录
</a-button>
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { getImageCaptcha } from '@/apis'
import { Message, type FormInstance, Modal } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import { type FormInstance, Message } from '@arco-design/web-vue'
import { useStorage } from '@vueuse/core'
import { getImageCaptcha } from '@/apis'
import { useUserStore } from '@/stores'
import { encryptByRsa } from '@/utils/encrypt'
const loginConfig = useStorage('login-config', {
@@ -62,13 +60,46 @@ const form = reactive({
uuid: '',
expired: false
})
const rules: FormInstance['rules'] = {
username: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }],
captcha: [{ required: true, message: '请输入验证码' }]
}
// 验证码过期定时器
let timer
const startTimer = (expireTime: number) => {
if (timer) {
clearTimeout(timer)
}
const remainingTime = expireTime - Date.now()
if (remainingTime <= 0) {
form.expired = true
return
}
timer = setTimeout(() => {
form.expired = true
}, remainingTime)
}
// 组件销毁时清理定时器
onBeforeUnmount(() => {
if (timer) {
clearTimeout(timer)
}
})
const captchaImgBase64 = ref()
// 获取验证码
const getCaptcha = () => {
getImageCaptcha().then((res) => {
const { uuid, img, expireTime } = res.data
form.uuid = uuid
captchaImgBase64.value = img
form.expired = false
startTimer(expireTime)
})
}
const userStore = useUserStore()
const router = useRouter()
const loading = ref(false)
@@ -102,40 +133,6 @@ const handleLogin = async () => {
}
}
const captchaImgBase64 = ref()
// 获取验证码
const getCaptcha = () => {
getImageCaptcha().then((res) => {
const { uuid, img, expireTime } = res.data
form.uuid = uuid
captchaImgBase64.value = img
form.expired = false
startTimer(expireTime)
})
}
// 验证码过期定时器
let timer
const startTimer = (expireTime: number) => {
if (timer) {
clearTimeout(timer)
}
const remainingTime = expireTime - Date.now()
if (remainingTime <= 0) {
form.expired = true
return
}
timer = setTimeout(() => {
form.expired = true
}, remainingTime)
}
// 组件销毁时清理定时器
onBeforeUnmount(() => {
if (timer) {
clearTimeout(timer)
}
})
onMounted(() => {
getCaptcha()
})

View File

@@ -25,8 +25,7 @@
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
>立即登录</a-button
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>
</a-space>
</a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts">
// import { getEmailCaptcha } from '@/apis'
import { Message, type FormInstance } from '@arco-design/web-vue'
import { type FormInstance, Message } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import * as Regexp from '@/utils/regexp'

View File

@@ -25,8 +25,7 @@
</a-form-item>
<a-form-item>
<a-space direction="vertical" fill class="w-full">
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
>立即登录</a-button
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button
>
</a-space>
</a-form-item>
@@ -35,7 +34,7 @@
<script setup lang="ts">
// import { getSmsCaptcha } from '@/apis'
import { Message, type FormInstance } from '@arco-design/web-vue'
import { type FormInstance, Message } from '@arco-design/web-vue'
import { useUserStore } from '@/stores'
import * as Regexp from '@/utils/regexp'

View File

@@ -89,11 +89,11 @@
</template>
<script setup lang="ts">
import { socialAuth } from '@/apis'
import Background from './components/background/index.vue'
import AccountLogin from './components/account/index.vue'
import PhoneLogin from './components/phone/index.vue'
import EmailLogin from './components/email/index.vue'
import { socialAuth } from '@/apis'
import { useAppStore } from '@/stores'
import { useDevice } from '@/hooks'

View File

@@ -5,9 +5,9 @@
</template>
<script setup lang="ts">
import { bindSocialAccount } from '@/apis'
import { Message } from '@arco-design/web-vue'
import { useRoute, useRouter } from 'vue-router'
import { bindSocialAccount } from '@/apis'
import { isLogin } from '@/utils/auth'
const route = useRoute()

View File

@@ -20,8 +20,8 @@ const route = useRoute()
const router = useRouter()
const PaneMap: Record<string, Component> = {
'1': LoginLog,
'2': OperationLog
1: LoginLog,
2: OperationLog
}
const activeKey = ref('1')

View File

@@ -6,7 +6,7 @@
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
:disabledTools="['size', 'setting']"
:disabled-tools="['size', 'setting']"
@filter-change="filterChange"
@refresh="search"
>
@@ -45,14 +45,42 @@
</template>
<script setup lang="ts">
import { exportLoginLog, listLog, type LogQuery } from '@/apis'
import dayjs from 'dayjs'
import { type LogQuery, exportLoginLog, listLog } from '@/apis'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import DateRangePicker from '@/components/DateRangePicker/index.vue'
import { useTable, useDownload } from '@/hooks'
import dayjs from 'dayjs'
import { useDownload, useTable } from '@/hooks'
defineOptions({ name: 'LoginLog' })
const queryForm = reactive<LogQuery>({
module: '登录',
createTime: [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
],
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
queryForm.status = undefined
search()
}
const columns: TableInstanceColumns[] = [
{
title: '序号',
@@ -90,22 +118,6 @@ const columns: TableInstanceColumns[] = [
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
]
const queryForm = reactive<LogQuery>({
module: '登录',
createTime: [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
],
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 过滤查询
const filterChange = (dataIndex, filteredValues) => {
try {
@@ -117,18 +129,6 @@ const filterChange = (dataIndex, filteredValues) => {
}
}
// 重置
const reset = () => {
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
queryForm.status = undefined
search()
}
// 导出
const onExport = () => {
useDownload(() => exportLoginLog(queryForm))

View File

@@ -2,9 +2,7 @@
<a-drawer v-model:visible="visible" title="日志详情" :width="720" :footer="false">
<a-descriptions title="基本信息" :column="2" size="large" class="general-description">
<a-descriptions-item label="日志 ID">{{ dataDetail?.id }}</a-descriptions-item>
<a-descriptions-item label="Trace ID"
>{{ dataDetail?.traceId }}<TextCopy :value="dataDetail?.traceId"
/></a-descriptions-item>
<a-descriptions-item label="Trace ID">{{ dataDetail?.traceId }}<TextCopy :value="dataDetail?.traceId" /></a-descriptions-item>
<a-descriptions-item label="操作人">{{ dataDetail?.createUserString }}</a-descriptions-item>
<a-descriptions-item label="操作时间">{{ dataDetail?.createTime }}</a-descriptions-item>
<a-descriptions-item label="操作内容">{{ dataDetail?.description }}</a-descriptions-item>
@@ -70,7 +68,7 @@
</template>
<script lang="ts" setup>
import { getLog, type LogDetailResp } from '@/apis'
import { type LogDetailResp, getLog } from '@/apis'
const dataId = ref('')
const dataDetail = ref<LogDetailResp>()

View File

@@ -7,7 +7,7 @@
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
column-resizable
:disabledTools="['size', 'setting']"
:disabled-tools="['size', 'setting']"
@filter-change="filterChange"
@refresh="search"
>
@@ -56,15 +56,43 @@
</template>
<script setup lang="ts">
import { listLog, exportOperationLog, type LogResp, type LogQuery } from '@/apis'
import dayjs from 'dayjs'
import OperationLogDetailDrawer from './OperationLogDetailDrawer.vue'
import { type LogQuery, type LogResp, exportOperationLog, listLog } from '@/apis'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import DateRangePicker from '@/components/DateRangePicker/index.vue'
import { useTable, useDownload } from '@/hooks'
import dayjs from 'dayjs'
import { useDownload, useTable } from '@/hooks'
defineOptions({ name: 'OperationLog' })
const queryForm = reactive<LogQuery>({
createTime: [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
],
sort: ['createTime,desc']
})
const {
loading,
tableData: dataList,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
queryForm.description = undefined
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
queryForm.status = undefined
search()
}
const columns: TableInstanceColumns[] = [
{
title: '序号',
@@ -102,21 +130,6 @@ const columns: TableInstanceColumns[] = [
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
]
const queryForm = reactive<LogQuery>({
createTime: [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
],
sort: ['createTime,desc']
})
const {
loading,
tableData: dataList,
pagination,
search
} = useTable((p) => listLog({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 过滤查询
const filterChange = (dataIndex, filteredValues) => {
try {
@@ -128,19 +141,6 @@ const filterChange = (dataIndex, filteredValues) => {
}
}
// 重置
const reset = () => {
queryForm.description = undefined
queryForm.ip = undefined
queryForm.createUserString = undefined
queryForm.createTime = [
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
queryForm.status = undefined
search()
}
// 导出
const onExport = () => {
useDownload(() => exportOperationLog(queryForm))

View File

@@ -8,7 +8,7 @@
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
:disabledTools="['size', 'setting']"
:disabled-tools="['size', 'setting']"
@refresh="search"
>
<template #custom-left>
@@ -44,8 +44,8 @@
</template>
<script setup lang="ts">
import { listOnlineUser, kickout, type OnlineUserQuery } from '@/apis'
import { Message } from '@arco-design/web-vue'
import { type OnlineUserQuery, kickout, listOnlineUser } from '@/apis'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import DateRangePicker from '@/components/DateRangePicker/index.vue'
import { useUserStore } from '@/stores'
@@ -57,6 +57,25 @@ defineOptions({ name: 'MonitorOnline' })
const userStore = useUserStore()
const currentToken = userStore.token
const queryForm = reactive<OnlineUserQuery>({
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search
} = useTable((p) => listOnlineUser({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
queryForm.nickname = undefined
queryForm.loginTime = undefined
search()
}
const columns: TableInstanceColumns[] = [
{
title: '序号',
@@ -79,24 +98,6 @@ const columns: TableInstanceColumns[] = [
}
]
const queryForm = reactive<OnlineUserQuery>({
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search
} = useTable((p) => listOnlineUser({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
queryForm.nickname = undefined
queryForm.loginTime = undefined
search()
}
// 强退
const handleKickout = (token: string) => {
kickout(token).then(() => {

View File

@@ -19,8 +19,8 @@
<script setup lang="ts">
// import { getSmsCaptcha, getEmailCaptcha, updateUserEmail, updateUserPhone } from '@/apis'
import { updateUserPassword } from '@/apis'
import { Message } from '@arco-design/web-vue'
import { updateUserPassword } from '@/apis'
import { encryptByRsa } from '@/utils/encrypt'
import { useUserStore } from '@/stores'
import { type Columns, GiForm } from '@/components/GiForm'
@@ -117,13 +117,6 @@ const { form, resetForm } = useForm({
rePassword: ''
})
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
resetCaptcha()
}
const captchaTimer = ref()
const captchaTime = ref(60)
const captchaBtnName = ref('获取验证码')
@@ -136,6 +129,13 @@ const resetCaptcha = () => {
captchaDisable.value = false
}
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
resetCaptcha()
}
const captchaLoading = ref(false)
// 获取验证码
const onCaptcha = async () => {

View File

@@ -95,10 +95,10 @@
</template>
<script setup lang="ts">
import { uploadAvatar } from '@/apis'
import BasicInfoUpdateModal from './BasicInfoUpdateModal.vue'
import { Message, type FileItem } from '@arco-design/web-vue'
import { type FileItem, Message } from '@arco-design/web-vue'
import { VueCropper } from 'vue-cropper'
import BasicInfoUpdateModal from './BasicInfoUpdateModal.vue'
import { uploadAvatar } from '@/apis'
import 'vue-cropper/dist/index.css'
import { useUserStore } from '@/stores'
import getAvatar from '@/utils/avatar'

View File

@@ -5,9 +5,9 @@
</template>
<script setup lang="ts">
import { updateUserBaseInfo } from '@/apis'
import { Message } from '@arco-design/web-vue'
import { GiForm, type Columns } from '@/components/GiForm'
import { updateUserBaseInfo } from '@/apis'
import { type Columns, GiForm } from '@/components/GiForm'
import { useForm } from '@/hooks'
import { useUserStore } from '@/stores'

View File

@@ -19,7 +19,7 @@
</div>
<div class="btn-wrapper">
<a-button
v-if="item.jumpMode == 'modal'"
v-if="item.jumpMode === 'modal'"
class="btn"
:type="item.status ? 'secondary' : 'primary'"
@click="onUpdate(item.type, item.status)"
@@ -35,9 +35,9 @@
</template>
<script lang="ts" setup>
import { listOption, type OptionResp, type SecurityConfigResp } from '@/apis'
import type { ModeItem } from '../type'
import VerifyModel from '../components/VerifyModel.vue'
import { type OptionResp, type SecurityConfigResp, listOption } from '@/apis'
import { useUserStore } from '@/stores'
const userStore = useUserStore()
@@ -48,7 +48,7 @@ modeList.value = [
{
title: '安全手机',
icon: 'phone-color',
value: `${userInfo.value.phone + ' ' || '手机号'}`,
value: `${`${userInfo.value.phone} ` || '手机号'}`,
subtitle: `可用于身份验证、密码找回、通知接收`,
type: 'phone',
jumpMode: 'modal',
@@ -58,7 +58,7 @@ modeList.value = [
{
title: '安全邮箱',
icon: 'email-color',
value: `${userInfo.value.email + ' ' || '邮箱'}`,
value: `${`${userInfo.value.email} ` || '邮箱'}`,
subtitle: `可用于身份验证、密码找回、通知接收`,
type: 'email',
jumpMode: 'modal',
@@ -96,7 +96,7 @@ const securityConfig = ref<SecurityConfigResp>({
const getDataList = async () => {
const { data } = await listOption({ code: Object.keys(securityConfig.value) })
securityConfig.value = data.reduce((obj: SecurityConfigResp, option: OptionResp) => {
obj[option.code] = { ...option, value: parseInt(option.value) }
obj[option.code] = { ...option, value: Number.parseInt(option.value) }
return obj
}, {})
}

View File

@@ -21,7 +21,7 @@
</div>
<div class="btn-wrapper">
<a-button
v-if="item.jumpMode == 'modal'"
v-if="item.jumpMode === 'modal'"
class="btn"
:type="item.status ? 'secondary' : 'primary'"
@click="onUpdate(item.type, item.status)"
@@ -29,7 +29,7 @@
{{ item.status ? '修改' : '绑定' }}
</a-button>
<a-button
v-else-if="item.jumpMode == 'link'"
v-else-if="item.jumpMode === 'link'"
class="btn"
:type="item.status ? 'secondary' : 'primary'"
@click="onBinding(item.type, item.status)"
@@ -44,13 +44,12 @@
</template>
<script setup lang="ts">
import { socialAuth, listUserSocial, unbindSocialAccount } from '@/apis'
import type { ModeItem } from '../type'
import VerifyModel from '../components/VerifyModel.vue'
import { listUserSocial, socialAuth, unbindSocialAccount } from '@/apis'
import { useUserStore } from '@/stores'
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)
const socialList = ref<any>([])
const modeList = ref<ModeItem[]>([])
@@ -58,18 +57,18 @@ modeList.value = [
{
title: '绑定 Gitee',
icon: 'gitee',
subtitle: `${socialList.value.some((el) => el == 'gitee') ? '' : '绑定后,'}可通过 Gitee 进行登录`,
subtitle: `${socialList.value.includes('gitee') ? '' : '绑定后,'}可通过 Gitee 进行登录`,
jumpMode: 'link',
type: 'gitee',
status: socialList.value.some((el) => el == 'gitee')
status: socialList.value.includes('gitee')
},
{
title: '绑定 GitHub',
icon: 'github',
subtitle: `${socialList.value.some((el) => el == 'gitee') ? '' : '绑定后,'}可通过 GitHub 进行登录`,
subtitle: `${socialList.value.includes('gitee') ? '' : '绑定后,'}可通过 GitHub 进行登录`,
type: 'github',
jumpMode: 'link',
status: socialList.value.some((el) => el == 'github')
status: socialList.value.includes('github')
}
]
@@ -81,7 +80,7 @@ const onBinding = (type: string, status: boolean) => {
})
} else {
unbindSocialAccount(type).then((res) => {
if (res.code == 200) {
if (res.code === 200) {
userStore.getInfo()
}
})

View File

@@ -85,7 +85,7 @@
v-model.trim="form.site_copyright"
placeholder="请输入版权信息"
:auto-size="{
minRows: 3
minRows: 3,
}"
show-word-limit
/>
@@ -130,8 +130,8 @@
</template>
<script lang="ts" setup>
import { listOption, updateOption, resetOptionValue, uploadFile, type OptionResp } from '@/apis'
import { Message, Modal, type FileItem, type FormInstance, type RequestOption } from '@arco-design/web-vue'
import { type FileItem, type FormInstance, Message, Modal, type RequestOption } from '@arco-design/web-vue'
import { type OptionResp, listOption, resetOptionValue, updateOption, uploadFile } from '@/apis'
import { useAppStore } from '@/stores'
import { useForm } from '@/hooks'

View File

@@ -1,5 +1,5 @@
<template>
<a-form style="margin-top: 20px" ref="formRef" :model="form" size="small" label-align="left" :disabled="!isUpdate">
<a-form ref="formRef" style="margin-top: 20px" :model="form" size="small" label-align="left" :disabled="!isUpdate">
<a-list size="small" :bordered="false">
<a-list-item style="border: none">
<a-form-item
@@ -7,43 +7,43 @@
:label="form.password_expiration_days.name"
field="password_expiration_days"
>
<a-input-number class="input-width" :min="0" :max="999" v-model="form.password_expiration_days.value">
<a-input-number v-model="form.password_expiration_days.value" class="input-width" :min="0" :max="999">
<template #append></template>
</a-input-number>
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_min_length.description" :label="form.password_min_length.name">
<a-input-number class="input-width" :min="8" :max="32" v-model="form.password_min_length.value" />
<a-input-number v-model="form.password_min_length.value" class="input-width" :min="8" :max="32" />
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_update_interval.description" :label="form.password_update_interval.name">
<a-input-number class="input-width" :min="0" :max="9999" v-model="form.password_update_interval.value">
<a-input-number v-model="form.password_update_interval.value" class="input-width" :min="0" :max="9999">
<template #append>分钟</template>
</a-input-number>
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_error_count.description" :label="form.password_error_count.name">
<a-input-number class="input-width" :min="0" :max="9999" v-model="form.password_error_count.value" />
<a-input-number v-model="form.password_error_count.value" class="input-width" :min="0" :max="9999" />
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_lock_minutes.description" :label="form.password_lock_minutes.name">
<a-input-number class="input-width" :min="0" :max="9999" v-model="form.password_lock_minutes.value">
<a-input-number v-model="form.password_lock_minutes.value" class="input-width" :min="0" :max="9999">
<template #append>分钟</template>
</a-input-number>
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_special_char.description" :label="form.password_special_char.name">
<a-switch type="round" :checked-value="1" :unchecked-value="0" v-model="form.password_special_char.value" />
<a-switch v-model="form.password_special_char.value" type="round" :checked-value="1" :unchecked-value="0" />
</a-form-item>
</a-list-item>
<a-list-item style="border: none">
<a-form-item :help="form.password_contain_name.description" :label="form.password_contain_name.name">
<a-switch type="round" :checked-value="1" :unchecked-value="0" v-model="form.password_contain_name.value" />
<a-switch v-model="form.password_contain_name.value" type="round" :checked-value="1" :unchecked-value="0" />
</a-form-item>
</a-list-item>
<a-list-item style="padding-top: 13px; border: none">
@@ -85,8 +85,8 @@
</template>
<script setup lang="ts">
import { listOption, updateOption, resetOptionValue, type SecurityConfigResp, type OptionResp } from '@/apis'
import { Message, Modal, type FormInstance } from '@arco-design/web-vue'
import { type FormInstance, Message, Modal } from '@arco-design/web-vue'
import { type OptionResp, type SecurityConfigResp, listOption, resetOptionValue, updateOption } from '@/apis'
const formRef = ref<FormInstance>()
@@ -100,23 +100,12 @@ const form = ref<SecurityConfigResp>({
password_update_interval: {}
})
// 重置
const reset = () => {
getDataList()
}
const isUpdate = ref(false)
// 修改
const onUpdate = () => {
isUpdate.value = true
}
// 取消
const handleCancel = () => {
reset()
isUpdate.value = false
}
const queryForm = {
code: Object.keys(form.value)
}
@@ -124,11 +113,22 @@ const queryForm = {
const getDataList = async () => {
const { data } = await listOption(queryForm)
form.value = data.reduce((obj: SecurityConfigResp, option: OptionResp) => {
obj[option.code] = { ...option, value: parseInt(option.value) }
obj[option.code] = { ...option, value: Number.parseInt(option.value) }
return obj
}, {})
}
// 重置
const reset = () => {
getDataList()
}
// 取消
const handleCancel = () => {
reset()
isUpdate.value = false
}
// 保存
const handleSave = async () => {
await updateOption(

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