初始化项目,自 v1.7.1 版本开始

This commit is contained in:
YunaiV
2023-02-11 00:44:00 +08:00
parent 11161afc1a
commit 56f3017baa
548 changed files with 52096 additions and 61 deletions

View File

@ -0,0 +1,60 @@
export interface ScrollToParams {
el: HTMLElement
to: number
position: string
duration?: number
callback?: () => void
}
const easeInOutQuad = (t: number, b: number, c: number, d: number) => {
t /= d / 2
if (t < 1) {
return (c / 2) * t * t + b
}
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const move = (el: HTMLElement, position: string, amount: number) => {
el[position] = amount
}
export function useScrollTo({
el,
position = 'scrollLeft',
to,
duration = 500,
callback
}: ScrollToParams) {
const isActiveRef = ref(false)
const start = el[position]
const change = to - start
const increment = 20
let currentTime = 0
function animateScroll() {
if (!unref(isActiveRef)) {
return
}
currentTime += increment
const val = easeInOutQuad(currentTime, start, change, duration)
move(el, position, val)
if (currentTime < duration && unref(isActiveRef)) {
requestAnimationFrame(animateScroll)
} else {
if (callback) {
callback()
}
}
}
function run() {
isActiveRef.value = true
animateScroll()
}
function stop() {
isActiveRef.value = false
}
return { start: run, stop }
}

27
src/hooks/web/useCache.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* 配置浏览器本地存储的方式,可直接存储对象数组。
*/
import WebStorageCache from 'web-storage-cache'
type CacheType = 'localStorage' | 'sessionStorage'
export const CACHE_KEY = {
IS_DARK: 'isDark',
USER: 'user',
LANG: 'lang',
THEME: 'theme',
LAYOUT: 'layout',
ROLE_ROUTERS: 'roleRouters',
DICT_CACHE: 'dictCache'
}
export const useCache = (type: CacheType = 'localStorage') => {
const wsCache: WebStorageCache = new WebStorageCache({
storage: type
})
return {
wsCache
}
}

View File

@ -0,0 +1,9 @@
import { ConfigGlobalTypes } from '@/types/configGlobal'
export const useConfigGlobal = () => {
const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes
return {
configGlobal
}
}

View File

@ -0,0 +1,293 @@
import { reactive } from 'vue'
import { AxiosPromise } from 'axios'
import { findIndex } from '@/utils'
import { eachTree, treeMap, filter } from '@/utils/tree'
import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
import { FormSchema } from '@/types/form'
import { TableColumn } from '@/types/table'
import { DescriptionsSchema } from '@/types/descriptions'
import { ComponentOptions, ComponentProps } from '@/types/components'
export type CrudSchema = Omit<TableColumn, 'children'> & {
isSearch?: boolean // 是否在查询显示
search?: CrudSearchParams // 查询的详细配置
isTable?: boolean // 是否在列表显示
table?: CrudTableParams // 列表的详细配置
isForm?: boolean // 是否在表单显示
form?: CrudFormParams // 表单的详细配置
isDetail?: boolean // 是否在详情显示
detail?: CrudDescriptionsParams // 详情的详细配置
children?: CrudSchema[]
dictType?: string // 字典类型
dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean
}
type CrudSearchParams = {
// 是否显示在查询项
show?: boolean
// 接口
api?: () => Promise<any>
} & Omit<FormSchema, 'field'>
type CrudTableParams = {
// 是否显示表头
show?: boolean
} & Omit<FormSchema, 'field'>
type CrudFormParams = {
// 是否显示表单项
show?: boolean
// 接口
api?: () => Promise<any>
} & Omit<FormSchema, 'field'>
type CrudDescriptionsParams = {
// 是否显示表单项
show?: boolean
} & Omit<DescriptionsSchema, 'field'>
interface AllSchemas {
searchSchema: FormSchema[]
tableColumns: TableColumn[]
formSchema: FormSchema[]
detailSchema: DescriptionsSchema[]
}
const { t } = useI18n()
// 过滤所有结构
export const useCrudSchemas = (
crudSchema: CrudSchema[]
): {
allSchemas: AllSchemas
} => {
// 所有结构数据
const allSchemas = reactive<AllSchemas>({
searchSchema: [],
tableColumns: [],
formSchema: [],
detailSchema: []
})
const searchSchema = filterSearchSchema(crudSchema, allSchemas)
allSchemas.searchSchema = searchSchema || []
const tableColumns = filterTableSchema(crudSchema)
allSchemas.tableColumns = tableColumns || []
const formSchema = filterFormSchema(crudSchema, allSchemas)
allSchemas.formSchema = formSchema
const detailSchema = filterDescriptionsSchema(crudSchema)
allSchemas.detailSchema = detailSchema
return {
allSchemas
}
}
// 过滤 Search 结构
const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
const searchSchema: FormSchema[] = []
// 获取字典列表队列
const searchRequestTask: Array<() => Promise<void>> = []
eachTree(crudSchema, (schemaItem: CrudSchema) => {
// 判断是否显示
if (schemaItem?.isSearch || schemaItem.search?.show) {
let component = schemaItem?.search?.component || 'Input'
const options: ComponentOptions[] = []
let comonentProps: ComponentProps = {}
if (schemaItem.dictType) {
const allOptions: ComponentOptions = { label: '全部', value: '' }
options.push(allOptions)
getDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
comonentProps = {
options: options
}
if (!schemaItem.search?.component) component = 'Select'
}
const searchSchemaItem = {
// 默认为 input
component: component,
componentProps: comonentProps,
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
}
if (searchSchemaItem.api) {
searchRequestTask.push(async () => {
const res = await (searchSchemaItem.api as () => AxiosPromise)()
if (res) {
const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => {
return v.field === searchSchemaItem.field
})
if (index !== -1) {
allSchemas.searchSchema[index]!.componentProps!.options = filterOptions(
res,
searchSchemaItem.componentProps.optionsAlias?.labelField
)
}
}
})
}
// 删除不必要的字段
delete searchSchemaItem.show
searchSchema.push(searchSchemaItem)
}
})
for (const task of searchRequestTask) {
task()
}
return searchSchema
}
// 过滤 table 结构
const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
const tableColumns = treeMap<CrudSchema>(crudSchema, {
conversion: (schema: CrudSchema) => {
if (schema?.isTable !== false && schema?.table?.show !== false) {
return {
...schema.table,
...schema
}
}
}
})
// 第一次过滤会有 undefined 所以需要二次过滤
return filter<TableColumn>(tableColumns as TableColumn[], (data) => {
if (data.children === void 0) {
delete data.children
}
return !!data.field
})
}
// 过滤 form 结构
const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
const formSchema: FormSchema[] = []
// 获取字典列表队列
const formRequestTask: Array<() => Promise<void>> = []
eachTree(crudSchema, (schemaItem: CrudSchema) => {
// 判断是否显示
if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {
let component = schemaItem?.form?.component || 'Input'
let defaultValue: any = ''
if (schemaItem.form?.value) {
defaultValue = schemaItem.form?.value
} else {
if (component === 'InputNumber') {
defaultValue = 0
}
}
let comonentProps: ComponentProps = {}
if (schemaItem.dictType) {
const options: ComponentOptions[] = []
if (schemaItem.dictClass && schemaItem.dictClass === 'number') {
getIntDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
} else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') {
getBoolDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
} else {
getDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
}
comonentProps = {
options: options
}
if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'
}
const formSchemaItem = {
// 默认为 input
component: component,
componentProps: comonentProps,
value: defaultValue,
...schemaItem.form,
field: schemaItem.field,
label: schemaItem.form?.label || schemaItem.label
}
if (formSchemaItem.api) {
formRequestTask.push(async () => {
const res = await (formSchemaItem.api as () => AxiosPromise)()
if (res) {
const index = findIndex(allSchemas.formSchema, (v: FormSchema) => {
return v.field === formSchemaItem.field
})
if (index !== -1) {
allSchemas.formSchema[index]!.componentProps!.options = filterOptions(
res,
formSchemaItem.componentProps.optionsAlias?.labelField
)
}
}
})
}
// 删除不必要的字段
delete formSchemaItem.show
formSchema.push(formSchemaItem)
}
})
for (const task of formRequestTask) {
task()
}
return formSchema
}
// 过滤 descriptions 结构
const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => {
const descriptionsSchema: FormSchema[] = []
eachTree(crudSchema, (schemaItem: CrudSchema) => {
// 判断是否显示
if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) {
const descriptionsSchemaItem = {
...schemaItem.detail,
field: schemaItem.field,
label: schemaItem.detail?.label || schemaItem.label
}
if (schemaItem.dictType) {
descriptionsSchemaItem.dictType = schemaItem.dictType
}
if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') {
// 优先使用 detail 下的配置,如果没有默认为 YYYY-MM-DD HH:mm:ss
descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat
? schemaItem?.detail?.dateFormat
: 'YYYY-MM-DD HH:mm:ss'
}
// 删除不必要的字段
delete descriptionsSchemaItem.show
descriptionsSchema.push(descriptionsSchemaItem)
}
})
return descriptionsSchema
}
// 给options添加国际化
const filterOptions = (options: Recordable, labelField?: string) => {
return options.map((v: Recordable) => {
if (labelField) {
v['labelField'] = t(v.labelField)
} else {
v['label'] = t(v.label)
}
return v
})
}

View File

@ -0,0 +1,18 @@
import variables from '@/styles/global.module.scss'
export const useDesign = () => {
const scssVariables = variables
/**
* @param scope 类名
* @returns 返回空间名-类名
*/
const getPrefixCls = (scope: string) => {
return `${scssVariables.namespace}-${scope}`
}
return {
variables: scssVariables,
getPrefixCls
}
}

22
src/hooks/web/useEmitt.ts Normal file
View File

@ -0,0 +1,22 @@
import mitt from 'mitt'
interface Option {
name: string // 事件名称
callback: Fn // 回调
}
const emitter = mitt()
export const useEmitt = (option?: Option) => {
if (option) {
emitter.on(option.name, option.callback)
onBeforeUnmount(() => {
emitter.off(option.name)
})
}
return {
emitter
}
}

94
src/hooks/web/useForm.ts Normal file
View File

@ -0,0 +1,94 @@
import type { Form, FormExpose } from '@/components/Form'
import type { ElForm } from 'element-plus'
import type { FormProps } from '@/components/Form/src/types'
import { FormSchema, FormSetPropsType } from '@/types/form'
export const useForm = (props?: FormProps) => {
// From实例
const formRef = ref<typeof Form & FormExpose>()
// ElForm实例
const elFormRef = ref<ComponentRef<typeof ElForm>>()
/**
* @param ref Form实例
* @param elRef ElForm实例
*/
const register = (ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) => {
formRef.value = ref
elFormRef.value = elRef
}
const getForm = async () => {
await nextTick()
const form = unref(formRef)
if (!form) {
console.error('The form is not registered. Please use the register method to register')
}
return form
}
// 一些内置的方法
const methods: {
setProps: (props: Recordable) => void
setValues: (data: Recordable) => void
getFormData: <T = Recordable | undefined>() => Promise<T>
setSchema: (schemaProps: FormSetPropsType[]) => void
addSchema: (formSchema: FormSchema, index?: number) => void
delSchema: (field: string) => void
} = {
setProps: async (props: FormProps = {}) => {
const form = await getForm()
form?.setProps(props)
if (props.model) {
form?.setValues(props.model)
}
},
setValues: async (data: Recordable) => {
const form = await getForm()
form?.setValues(data)
},
/**
* @param schemaProps 需要设置的schemaProps
*/
setSchema: async (schemaProps: FormSetPropsType[]) => {
const form = await getForm()
form?.setSchema(schemaProps)
},
/**
* @param formSchema 需要新增数据
* @param index 在哪里新增
*/
addSchema: async (formSchema: FormSchema, index?: number) => {
const form = await getForm()
form?.addSchema(formSchema, index)
},
/**
* @param field 删除哪个数据
*/
delSchema: async (field: string) => {
const form = await getForm()
form?.delSchema(field)
},
/**
* @returns form data
*/
getFormData: async <T = Recordable>(): Promise<T> => {
const form = await getForm()
return form?.formModel as T
}
}
props && methods.setProps(props)
return {
register,
elFormRef,
methods
}
}

52
src/hooks/web/useI18n.ts Normal file
View File

@ -0,0 +1,52 @@
import { i18n } from '@/plugins/vueI18n'
type I18nGlobalTranslation = {
(key: string): string
(key: string, locale: string): string
(key: string, locale: string, list: unknown[]): string
(key: string, locale: string, named: Record<string, unknown>): string
(key: string, list: unknown[]): string
(key: string, named: Record<string, unknown>): string
}
type I18nTranslationRestParameters = [string, any]
const getKey = (namespace: string | undefined, key: string) => {
if (!namespace) {
return key
}
if (key.startsWith(namespace)) {
return key
}
return `${namespace}.${key}`
}
export const useI18n = (
namespace?: string
): {
t: I18nGlobalTranslation
} => {
const normalFn = {
t: (key: string) => {
return getKey(namespace, key)
}
}
if (!i18n) {
return normalFn
}
const { t, ...methods } = i18n.global
const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
if (!key) return ''
if (!key.includes('.') && !namespace) return key
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters))
}
return {
...methods,
t: tFn
}
}
export const t = (key: string) => key

8
src/hooks/web/useIcon.ts Normal file
View File

@ -0,0 +1,8 @@
import { h } from 'vue'
import type { VNode } from 'vue'
import { Icon } from '@/components/Icon'
import { IconTypes } from '@/types/icon'
export const useIcon = (props: IconTypes): VNode => {
return h(Icon, props)
}

47
src/hooks/web/useIntro.ts Normal file
View File

@ -0,0 +1,47 @@
import introJs from 'intro.js'
import { IntroJs, Step, Options } from 'intro.js'
import 'intro.js/introjs.css'
import { useDesign } from '@/hooks/web/useDesign'
export const useIntro = (setps?: Step[], options?: Options) => {
const { t } = useI18n()
const { variables } = useDesign()
const defaultSetps: Step[] = setps || [
{
element: `#${variables.namespace}-menu`,
title: t('common.menu'),
intro: t('common.menuDes'),
position: 'right'
},
{
element: `#${variables.namespace}-tool-header`,
title: t('common.tool'),
intro: t('common.toolDes'),
position: 'left'
},
{
element: `#${variables.namespace}-tags-view`,
title: t('common.tagsView'),
intro: t('common.tagsViewDes'),
position: 'bottom'
}
]
const defaultOptions: Options = options || {
prevLabel: t('common.prevLabel'),
nextLabel: t('common.nextLabel'),
skipLabel: t('common.skipLabel'),
doneLabel: t('common.doneLabel')
}
const introRef: IntroJs = introJs()
introRef.addSteps(defaultSetps).setOptions(defaultOptions)
return {
introRef
}
}

View File

@ -0,0 +1,35 @@
import { i18n } from '@/plugins/vueI18n'
import { useLocaleStoreWithOut } from '@/store/modules/locale'
import { setHtmlPageLang } from '@/plugins/vueI18n/helper'
const setI18nLanguage = (locale: LocaleType) => {
const localeStore = useLocaleStoreWithOut()
if (i18n.mode === 'legacy') {
i18n.global.locale = locale
} else {
;(i18n.global.locale as any).value = locale
}
localeStore.setCurrentLocale({
lang: locale
})
setHtmlPageLang(locale)
}
export const useLocale = () => {
// Switching the language will change the locale of useI18n
// And submit to configuration modification
const changeLocale = async (locale: LocaleType) => {
const globalI18n = i18n.global
const langModule = await import(`../../locales/${locale}.ts`)
globalI18n.setLocaleMessage(locale, langModule.default)
setI18nLanguage(locale)
}
return {
changeLocale
}
}

View File

@ -0,0 +1,95 @@
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import { useI18n } from './useI18n'
export const useMessage = () => {
const { t } = useI18n()
return {
// 消息提示
info(content: string) {
ElMessage.info(content)
},
// 错误消息
error(content: string) {
ElMessage.error(content)
},
// 成功消息
success(content: string) {
ElMessage.success(content)
},
// 警告消息
warning(content: string) {
ElMessage.warning(content)
},
// 弹出提示
alert(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'))
},
// 错误提示
alertError(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' })
},
// 成功提示
alertSuccess(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' })
},
// 警告提示
alertWarning(content: string) {
ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' })
},
// 通知提示
notify(content: string) {
ElNotification.info(content)
},
// 错误通知
notifyError(content: string) {
ElNotification.error(content)
},
// 成功通知
notifySuccess(content: string) {
ElNotification.success(content)
},
// 警告通知
notifyWarning(content: string) {
ElNotification.warning(content)
},
// 确认窗体
confirm(content: string, tip?: string) {
return ElMessageBox.confirm(content, tip ? tip : t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
},
// 删除窗体
delConfirm(content?: string, tip?: string) {
return ElMessageBox.confirm(
content ? content : t('common.delMessage'),
tip ? tip : t('common.confirmTitle'),
{
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}
)
},
// 导出窗体
exportConfirm(content?: string, tip?: string) {
return ElMessageBox.confirm(
content ? content : t('common.exportMessage'),
tip ? tip : t('common.confirmTitle'),
{
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}
)
},
// 提交内容
prompt(content: string, tip: string) {
return ElMessageBox.prompt(content, tip, {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
}
}
}

View File

@ -0,0 +1,33 @@
import { useCssVar } from '@vueuse/core'
import type { NProgressOptions } from 'nprogress'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
export const useNProgress = () => {
NProgress.configure({ showSpinner: false } as NProgressOptions)
const initColor = async () => {
await nextTick()
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
if (bar) {
bar.style.background = unref(primaryColor.value)
}
}
initColor()
const start = () => {
NProgress.start()
}
const done = () => {
NProgress.done()
}
return {
start,
done
}
}

View File

@ -0,0 +1,18 @@
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
export const usePageLoading = () => {
const loadStart = () => {
appStore.setPageLoading(true)
}
const loadDone = () => {
appStore.setPageLoading(false)
}
return {
loadStart,
loadDone
}
}

223
src/hooks/web/useTable.ts Normal file
View File

@ -0,0 +1,223 @@
import download from '@/utils/download'
import { Table, TableExpose } from '@/components/Table'
import { ElMessage, ElMessageBox, ElTable } from 'element-plus'
import { computed, nextTick, reactive, ref, unref, watch } from 'vue'
import type { TableProps } from '@/components/Table/src/types'
import { TableSetPropsType } from '@/types/table'
const { t } = useI18n()
interface ResponseType<T = any> {
list: T[]
total?: number
}
interface UseTableConfig<T = any> {
getListApi: (option: any) => Promise<T>
delListApi?: (option: any) => Promise<T>
exportListApi?: (option: any) => Promise<T>
// 返回数据格式配置
response?: ResponseType
// 默认传递的参数
defaultParams?: Recordable
props?: TableProps
}
interface TableObject<T = any> {
pageSize: number
currentPage: number
total: number
tableList: T[]
params: any
loading: boolean
exportLoading: boolean
currentRow: Nullable<T>
}
export const useTable = <T = any>(config?: UseTableConfig<T>) => {
const tableObject = reactive<TableObject<T>>({
// 页数
pageSize: 10,
// 当前页
currentPage: 1,
// 总条数
total: 10,
// 表格数据
tableList: [],
// AxiosConfig 配置
params: {
...(config?.defaultParams || {})
},
// 加载中
loading: true,
// 导出加载中
exportLoading: false,
// 当前行的数据
currentRow: null
})
const paramsObj = computed(() => {
return {
...tableObject.params,
pageSize: tableObject.pageSize,
pageNo: tableObject.currentPage
}
})
watch(
() => tableObject.currentPage,
() => {
methods.getList()
}
)
watch(
() => tableObject.pageSize,
() => {
// 当前页不为1时修改页数后会导致多次调用getList方法
if (tableObject.currentPage === 1) {
methods.getList()
} else {
tableObject.currentPage = 1
methods.getList()
}
}
)
// Table实例
const tableRef = ref<typeof Table & TableExpose>()
// ElTable实例
const elTableRef = ref<ComponentRef<typeof ElTable>>()
const register = (ref: typeof Table & TableExpose, elRef: ComponentRef<typeof ElTable>) => {
tableRef.value = ref
elTableRef.value = elRef
}
const getTable = async () => {
await nextTick()
const table = unref(tableRef)
if (!table) {
console.error('The table is not registered. Please use the register method to register')
}
return table
}
const delData = async (ids: string | number | string[] | number[]) => {
let idsLength = 1
if (ids instanceof Array) {
idsLength = ids.length
await Promise.all(
ids.map(async (id: string | number) => {
await (config?.delListApi && config?.delListApi(id))
})
)
} else {
await (config?.delListApi && config?.delListApi(ids))
}
ElMessage.success(t('common.delSuccess'))
// 计算出临界点
tableObject.currentPage =
tableObject.total % tableObject.pageSize === idsLength || tableObject.pageSize === 1
? tableObject.currentPage > 1
? tableObject.currentPage - 1
: tableObject.currentPage
: tableObject.currentPage
await methods.getList()
}
const methods = {
getList: async () => {
tableObject.loading = true
const res = await config?.getListApi(unref(paramsObj)).finally(() => {
tableObject.loading = false
})
if (res) {
tableObject.tableList = (res as unknown as ResponseType).list
if ((res as unknown as ResponseType).total) {
tableObject.total = (res as unknown as ResponseType).total as unknown as number
}
}
},
setProps: async (props: TableProps = {}) => {
const table = await getTable()
table?.setProps(props)
},
setColumn: async (columnProps: TableSetPropsType[]) => {
const table = await getTable()
table?.setColumn(columnProps)
},
getSelections: async () => {
const table = await getTable()
return (table?.selections || []) as T[]
},
// 与Search组件结合
setSearchParams: (data: Recordable) => {
tableObject.params = Object.assign(tableObject.params, {
pageSize: tableObject.pageSize,
pageNo: 1,
...data
})
// 页码不等于1时更新页码重新获取数据页码等于1时重新获取数据
if (tableObject.currentPage !== 1) {
tableObject.currentPage = 1
} else {
methods.getList()
}
},
// 删除数据
delList: async (
ids: string | number | string[] | number[],
multiple: boolean,
message = true
) => {
const tableRef = await getTable()
if (multiple) {
if (!tableRef?.selections.length) {
ElMessage.warning(t('common.delNoData'))
return
}
}
if (message) {
ElMessageBox.confirm(t('common.delMessage'), t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}).then(async () => {
await delData(ids)
})
} else {
await delData(ids)
}
},
// 导出列表
exportList: async (fileName: string) => {
tableObject.exportLoading = true
ElMessageBox.confirm(t('common.exportMessage'), t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
const res = await config?.exportListApi?.(unref(paramsObj) as unknown as T)
if (res) {
download.excel(res as unknown as Blob, fileName)
}
})
.finally(() => {
tableObject.exportLoading = false
})
}
}
config?.props && methods.setProps(config.props)
return {
register,
elTableRef,
tableObject,
methods
}
}

View File

@ -0,0 +1,47 @@
import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core'
import { useLocaleStoreWithOut } from '@/store/modules/locale'
const TIME_AGO_MESSAGE_MAP: {
'zh-CN': UseTimeAgoMessages
en: UseTimeAgoMessages
} = {
'zh-CN': {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n}` : n),
future: (n) => (n.match(/\d/) ? `${n}` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n}`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n}`),
week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n}`),
hour: (n) => `${n} 小时`,
minute: (n) => `${n} 分钟`,
second: (n) => `${n}`
},
en: {
justNow: 'just now',
past: (n) => (n.match(/\d/) ? `${n} ago` : n),
future: (n) => (n.match(/\d/) ? `in ${n}` : n),
month: (n, past) =>
n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`,
year: (n, past) =>
n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`,
day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`),
week: (n, past) =>
n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`,
hour: (n) => `${n} hour${n > 1 ? 's' : ''}`,
minute: (n) => `${n} minute${n > 1 ? 's' : ''}`,
second: (n) => `${n} second${n > 1 ? 's' : ''}`
}
}
export const useTimeAgo = (time: Date | number | string) => {
const localeStore = useLocaleStoreWithOut()
const currentLocale = computed(() => localeStore.getCurrentLocale)
const timeAgo = useTimeAgoCore(time, {
messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang]
})
return timeAgo
}

24
src/hooks/web/useTitle.ts Normal file
View File

@ -0,0 +1,24 @@
import { watch, ref } from 'vue'
import { isString } from '@/utils/is'
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
export const useTitle = (newTitle?: string) => {
const { t } = useI18n()
const title = ref(
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
)
watch(
title,
(n, o) => {
if (isString(n) && n !== o && document) {
document.title = n
}
},
{ immediate: true }
)
return title
}

View File

@ -0,0 +1,62 @@
const { t } = useI18n()
type Callback = (error?: string | Error | undefined) => void
interface LengthRange {
min: number
max: number
message: string
}
export const useValidator = () => {
const required = (message?: string) => {
return {
required: true,
message: message || t('common.required')
}
}
const lengthRange = (val: any, callback: Callback, options: LengthRange) => {
const { min, max, message } = options
if (val.length < min || val.length > max) {
callback(new Error(message))
} else {
callback()
}
}
const notSpace = (val: any, callback: Callback, message: string) => {
// 用户名不能有空格
if (val.indexOf(' ') !== -1) {
callback(new Error(message))
} else {
callback()
}
}
const notSpecialCharacters = (val: any, callback: Callback, message: string) => {
// 密码不能是特殊字符
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
callback(new Error(message))
} else {
callback()
}
}
// 两个字符串是否想等
const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
if (val1 === val2) {
callback()
} else {
callback(new Error(message))
}
}
return {
required,
lengthRange,
notSpace,
notSpecialCharacters,
isEqual
}
}

View File

@ -0,0 +1,354 @@
import {
FormItemRenderOptions,
VxeColumnPropTypes,
VxeFormItemProps,
VxeGridPropTypes,
VxeTableDefines
} from 'vxe-table'
import { eachTree } from 'xe-utils'
import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
import { FormSchema } from '@/types/form'
import { VxeTableColumn } from '@/types/table'
import { ComponentOptions } from '@/types/components'
import { DescriptionsSchema } from '@/types/descriptions'
export type VxeCrudSchema = {
primaryKey?: string // 主键ID
primaryTitle?: string // 主键标题 默认为序号
primaryType?: VxeColumnPropTypes.Type | 'id' // 还支持 "id" | "seq" | "radio" | "checkbox" | "expand" | "html" | null
firstColumn?: VxeColumnPropTypes.Type // 第一列显示类型
action?: boolean // 是否开启表格内右侧操作栏插槽
actionTitle?: string // 操作栏标题 默认为操作
actionWidth?: string // 操作栏插槽宽度,一般2个字带图标 text 类型按钮 50-70
columns: VxeCrudColumns[]
searchSpan?: number
}
type VxeCrudColumns = Omit<VxeTableColumn, 'children'> & {
field: string // 字段名
title?: string // 标题名
formatter?: VxeColumnPropTypes.Formatter // vxe formatter格式化
isSearch?: boolean // 是否在查询显示
search?: CrudSearchParams // 查询的详细配置
isTable?: boolean // 是否在列表显示
table?: CrudTableParams // 列表的详细配置
isForm?: boolean // 是否在表单显示
form?: CrudFormParams // 表单的详细配置
isDetail?: boolean // 是否在详情显示
detail?: CrudDescriptionsParams // 详情的详细配置
print?: CrudPrintParams // vxe 打印的字段
children?: VxeCrudColumns[] // 子级
dictType?: string // 字典类型
dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean
}
type CrudSearchParams = {
// 是否显示在查询项
show?: boolean
} & Omit<VxeFormItemProps, 'field'>
type CrudTableParams = {
// 是否显示表头
show?: boolean
} & Omit<VxeTableDefines.ColumnOptions, 'field'>
type CrudFormParams = {
// 是否显示表单项
show?: boolean
} & Omit<FormSchema, 'field'>
type CrudDescriptionsParams = {
// 是否显示表单项
show?: boolean
} & Omit<DescriptionsSchema, 'field'>
type CrudPrintParams = {
// 是否显示打印项
show?: boolean
} & Omit<VxeTableDefines.ColumnInfo[], 'field'>
export type VxeAllSchemas = {
searchSchema: VxeFormItemProps[]
tableSchema: VxeGridPropTypes.Columns
formSchema: FormSchema[]
detailSchema: DescriptionsSchema[]
printSchema: VxeTableDefines.ColumnInfo[]
}
// 过滤所有结构
export const useVxeCrudSchemas = (
crudSchema: VxeCrudSchema
): {
allSchemas: VxeAllSchemas
} => {
// 所有结构数据
const allSchemas = reactive<VxeAllSchemas>({
searchSchema: [],
tableSchema: [],
formSchema: [],
detailSchema: [],
printSchema: []
})
const searchSchema = filterSearchSchema(crudSchema)
allSchemas.searchSchema = searchSchema || []
const tableSchema = filterTableSchema(crudSchema)
allSchemas.tableSchema = tableSchema || []
const formSchema = filterFormSchema(crudSchema)
allSchemas.formSchema = formSchema
const detailSchema = filterDescriptionsSchema(crudSchema)
allSchemas.detailSchema = detailSchema
const printSchema = filterPrintSchema(crudSchema)
allSchemas.printSchema = printSchema
return {
allSchemas
}
}
// 过滤 Search 结构
const filterSearchSchema = (crudSchema: VxeCrudSchema): VxeFormItemProps[] => {
const { t } = useI18n()
const span = crudSchema.searchSpan ? crudSchema.searchSpan : 6
const spanLength = 24 / span
const searchSchema: VxeFormItemProps[] = []
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
// 判断是否显示
if (schemaItem?.isSearch || schemaItem.search?.show) {
let itemRenderName = schemaItem?.search?.itemRender?.name || '$input'
const options: any[] = []
let itemRender: FormItemRenderOptions = {}
if (schemaItem.dictType) {
const allOptions = { label: '全部', value: '' }
options.push(allOptions)
getDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
itemRender.options = options
if (!schemaItem?.search?.itemRender?.name) itemRenderName = '$select'
itemRender = {
name: itemRenderName,
options: options,
props: { placeholder: t('common.selectText') }
}
} else {
if (schemaItem.search?.itemRender) {
itemRender = schemaItem.search.itemRender
} else {
itemRender = {
name: itemRenderName,
props:
itemRenderName == '$input'
? { placeholder: t('common.inputText') }
: { placeholder: t('common.selectText') }
}
}
}
const searchSchemaItem = {
// 默认为 input
folding: searchSchema.length > spanLength - 1,
itemRender: schemaItem.itemRender ? schemaItem.itemRender : itemRender,
field: schemaItem.field,
title: schemaItem.search?.title || schemaItem.title,
slots: schemaItem.search?.slots,
span: span
}
searchSchema.push(searchSchemaItem)
}
})
if (searchSchema.length > 0) {
// 添加搜索按钮
const buttons: VxeFormItemProps = {
span: 24,
align: 'right',
collapseNode: searchSchema.length > spanLength,
itemRender: {
name: '$buttons',
children: [
{ props: { type: 'submit', content: t('common.query'), status: 'primary' } },
{ props: { type: 'reset', content: t('common.reset') } }
]
}
}
searchSchema.push(buttons)
}
return searchSchema
}
// 过滤 table 结构
const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns => {
const { t } = useI18n()
const tableSchema: VxeGridPropTypes.Columns = []
// 第一列
if (crudSchema.firstColumn) {
const tableSchemaItem = {
type: crudSchema.firstColumn,
width: '50px'
}
tableSchema.push(tableSchemaItem)
}
// 主键ID
if (crudSchema.primaryKey && crudSchema.primaryType) {
const primaryTitle = crudSchema.primaryTitle ? crudSchema.primaryTitle : t('common.index')
const primaryWidth = primaryTitle.length * 30 + 'px'
let tableSchemaItem: { [x: string]: any } = {
title: primaryTitle,
field: crudSchema.primaryKey,
width: primaryWidth
}
if (crudSchema.primaryType != 'id') {
tableSchemaItem = {
...tableSchemaItem,
type: crudSchema.primaryType
}
}
tableSchema.push(tableSchemaItem)
}
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
// 判断是否显示
if (schemaItem?.isTable !== false && schemaItem?.table?.show !== false) {
const tableSchemaItem = {
...schemaItem.table,
field: schemaItem.field,
title: schemaItem.table?.title || schemaItem.title,
minWidth: '80px'
}
tableSchemaItem.showOverflow = 'tooltip'
if (schemaItem?.formatter) {
tableSchemaItem.formatter = schemaItem.formatter
tableSchemaItem.width = tableSchemaItem.width ? tableSchemaItem.width : 160
}
if (schemaItem?.dictType) {
tableSchemaItem.cellRender = {
name: 'XDict',
content: schemaItem.dictType
}
tableSchemaItem.width = tableSchemaItem.width ? tableSchemaItem.width : 160
}
tableSchema.push(tableSchemaItem)
}
})
// 操作栏插槽
if (crudSchema.action && crudSchema.action == true) {
const tableSchemaItem = {
title: crudSchema.actionTitle ? crudSchema.actionTitle : t('table.action'),
field: 'actionbtns',
fixed: 'right' as unknown as VxeColumnPropTypes.Fixed,
width: crudSchema.actionWidth ? crudSchema.actionWidth : '200px',
slots: {
default: 'actionbtns_default'
}
}
tableSchema.push(tableSchemaItem)
}
return tableSchema
}
// 过滤 form 结构
const filterFormSchema = (crudSchema: VxeCrudSchema): FormSchema[] => {
const formSchema: FormSchema[] = []
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
// 判断是否显示
if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {
// 默认为 input
let component = schemaItem?.form?.component || 'Input'
let defaultValue: any = ''
if (schemaItem.form?.value) {
defaultValue = schemaItem.form?.value
} else {
if (component === 'InputNumber') {
defaultValue = 0
}
}
let comonentProps = {}
if (schemaItem.dictType) {
const options: ComponentOptions[] = []
if (schemaItem.dictClass && schemaItem.dictClass === 'number') {
getIntDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
} else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') {
getBoolDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
} else {
getDictOptions(schemaItem.dictType).forEach((dict) => {
options.push(dict)
})
}
comonentProps = {
options: options
}
if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'
}
const formSchemaItem = {
component: component,
componentProps: comonentProps,
value: defaultValue,
...schemaItem.form,
field: schemaItem.field,
label: schemaItem.form?.label || schemaItem.title
}
formSchema.push(formSchemaItem)
}
})
return formSchema
}
// 过滤 descriptions 结构
const filterDescriptionsSchema = (crudSchema: VxeCrudSchema): DescriptionsSchema[] => {
const descriptionsSchema: DescriptionsSchema[] = []
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
// 判断是否显示
if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) {
const descriptionsSchemaItem = {
...schemaItem.detail,
field: schemaItem.field,
label: schemaItem.detail?.label || schemaItem.title
}
if (schemaItem.dictType) {
descriptionsSchemaItem.dictType = schemaItem.dictType
}
if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') {
// 优先使用 detail 下的配置,如果没有默认为 YYYY-MM-DD HH:mm:ss
descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat
? schemaItem?.detail?.dateFormat
: 'YYYY-MM-DD HH:mm:ss'
}
descriptionsSchema.push(descriptionsSchemaItem)
}
})
return descriptionsSchema
}
// 过滤 打印 结构
const filterPrintSchema = (crudSchema: VxeCrudSchema): any[] => {
const printSchema: any[] = []
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
// 判断是否显示
if (schemaItem?.print?.show !== false) {
const printSchemaItem = {
field: schemaItem.field
}
printSchema.push(printSchemaItem)
}
})
return printSchema
}

264
src/hooks/web/useVxeGrid.ts Normal file
View File

@ -0,0 +1,264 @@
import { computed, nextTick, reactive } from 'vue'
import { SizeType, VxeGridProps, VxeTablePropTypes } from 'vxe-table'
import { useAppStore } from '@/store/modules/app'
import { VxeAllSchemas } from './useVxeCrudSchemas'
import download from '@/utils/download'
const { t } = useI18n()
const message = useMessage() // 消息弹窗
interface UseVxeGridConfig<T = any> {
allSchemas: VxeAllSchemas
height?: number // 高度 默认730
topActionSlots?: boolean // 是否开启表格内顶部操作栏插槽
treeConfig?: VxeTablePropTypes.TreeConfig // 树形表单配置
isList?: boolean // 是否不带分页的list
getListApi: (option: any) => Promise<T> // 获取列表接口
getAllListApi?: (option: any) => Promise<T> // 获取全部数据接口 用于VXE导出
deleteApi?: (option: any) => Promise<T> // 删除接口
exportListApi?: (option: any) => Promise<T> // 导出接口
exportName?: string // 导出文件夹名称
queryParams?: any // 其他查询参数
}
const appStore = useAppStore()
const currentSize = computed(() => {
let resSize: SizeType = 'small'
const appsize = appStore.getCurrentSize
switch (appsize) {
case 'large':
resSize = 'medium'
break
case 'default':
resSize = 'small'
break
case 'small':
resSize = 'mini'
break
}
return resSize
})
export const useVxeGrid = <T = any>(config?: UseVxeGridConfig<T>) => {
/**
* grid options 初始化
*/
const gridOptions = reactive<VxeGridProps<any>>({
loading: true,
size: currentSize as any,
height: config?.height ? config.height : 730,
rowConfig: {
isCurrent: true, // 当鼠标点击行时,是否要高亮当前行
isHover: true // 当鼠标移到行时,是否要高亮当前行
},
toolbarConfig: {
slots:
!config?.topActionSlots && config?.topActionSlots != false
? { buttons: 'toolbar_buttons' }
: {}
},
printConfig: {
columns: config?.allSchemas.printSchema
},
formConfig: {
enabled: true,
titleWidth: 100,
titleAlign: 'right',
items: config?.allSchemas.searchSchema
},
columns: config?.allSchemas.tableSchema,
proxyConfig: {
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
props: { result: 'list', total: 'total' },
ajax: {
query: ({ page, form }) => {
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
if (config?.queryParams) {
queryParams = Object.assign(queryParams, config.queryParams)
}
if (!config?.treeConfig) {
queryParams.pageSize = page.pageSize
queryParams.pageNo = page.currentPage
}
gridOptions.loading = false
return new Promise(async (resolve) => {
resolve(await config?.getListApi(queryParams))
})
},
delete: ({ body }) => {
return new Promise(async (resolve) => {
if (config?.deleteApi) {
resolve(await config?.deleteApi(JSON.stringify(body)))
} else {
Promise.reject('未设置deleteApi')
}
})
},
queryAll: ({ form }) => {
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(form)))
return new Promise(async (resolve) => {
if (config?.getAllListApi) {
resolve(await config?.getAllListApi(queryParams))
} else {
resolve(await config?.getListApi(queryParams))
}
})
}
}
},
exportConfig: {
filename: config?.exportName,
// 默认选中类型
type: 'csv',
// 自定义数据量列表
modes: config?.getAllListApi ? ['current', 'all'] : ['current'],
columns: config?.allSchemas.printSchema
}
})
if (config?.treeConfig) {
gridOptions.treeConfig = config.treeConfig
} else if (config?.isList) {
gridOptions.proxyConfig = {
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
props: { result: 'data' },
ajax: {
query: ({ form }) => {
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
if (config?.queryParams) {
queryParams = Object.assign(queryParams, config.queryParams)
}
gridOptions.loading = false
return new Promise(async (resolve) => {
resolve(await config?.getListApi(queryParams))
})
}
}
}
} else {
gridOptions.pagerConfig = {
border: false, // 带边框
background: true, // 带背景颜色
perfect: false, // 配套的样式
pageSize: 10, // 每页大小
pagerCount: 7, // 显示页码按钮的数量
autoHidden: false, // 当只有一页时自动隐藏
pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
layouts: [
'PrevJump',
'PrevPage',
'JumpNumber',
'NextPage',
'NextJump',
'Sizes',
'FullJump',
'Total'
]
}
}
/**
* 刷新列表
* @param ref
* @returns
*/
const getList = async (ref) => {
if (!ref) {
console.error('未传入gridRef')
return
}
await nextTick()
ref.value.commitProxy('query')
}
// 获取查询参数
const getSearchData = async (ref) => {
if (!ref) {
console.error('未传入gridRef')
return
}
await nextTick()
const queryParams = Object.assign(
{},
JSON.parse(JSON.stringify(ref.value.getProxyInfo()?.form))
)
return queryParams
}
/**
* 删除
* @param ref
* @param ids rowid
* @returns
*/
const deleteData = async (ref, ids: string | number) => {
if (!ref) {
console.error('未传入gridRef')
return
}
if (!config?.deleteApi) {
console.error('未传入delListApi')
return
}
await nextTick()
return new Promise(async () => {
message.delConfirm().then(async () => {
await (config?.deleteApi && config?.deleteApi(ids))
message.success(t('common.delSuccess'))
// 刷新列表
ref.value.commitProxy('query')
})
})
}
/**
* 导出
* @param ref
* @param fileName 文件名默认excel.xls
* @returns
*/
const exportList = async (ref, fileName?: string) => {
if (!ref) {
console.error('未传入gridRef')
return
}
if (!config?.exportListApi) {
console.error('未传入exportListApi')
return
}
await nextTick()
const queryParams = Object.assign(
{},
JSON.parse(JSON.stringify(ref.value?.getProxyInfo()?.form))
)
message.exportConfirm().then(async () => {
const res = await (config?.exportListApi && config?.exportListApi(queryParams))
download.excel(res as unknown as Blob, fileName ? fileName : 'excel.xls')
})
}
/**
* 表格最大/最小化
* @param ref
* @returns
*/
const zoom = async (ref) => {
if (!ref) {
console.error('未传入gridRef')
return
}
await nextTick()
ref.value.zoom(!ref.value.isMaximized())
}
return {
gridOptions,
getList,
getSearchData,
deleteData,
exportList,
zoom
}
}

View File

@ -0,0 +1,55 @@
const domSymbol = Symbol('watermark-dom')
export function useWatermark(appendEl: HTMLElement | null = document.body) {
let func: Fn = () => {}
const id = domSymbol.toString()
const clear = () => {
const domId = document.getElementById(id)
if (domId) {
const el = appendEl
el && el.removeChild(domId)
}
window.removeEventListener('resize', func)
}
const createWatermark = (str: string) => {
clear()
const can = document.createElement('canvas')
can.width = 300
can.height = 240
const cans = can.getContext('2d')
if (cans) {
cans.rotate((-20 * Math.PI) / 120)
cans.font = '15px Vedana'
cans.fillStyle = 'rgba(0, 0, 0, 0.15)'
cans.textAlign = 'left'
cans.textBaseline = 'middle'
cans.fillText(str, can.width / 20, can.height)
}
const div = document.createElement('div')
div.id = id
div.style.pointerEvents = 'none'
div.style.top = '0px'
div.style.left = '0px'
div.style.position = 'absolute'
div.style.zIndex = '100000000'
div.style.width = document.documentElement.clientWidth + 'px'
div.style.height = document.documentElement.clientHeight + 'px'
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
const el = appendEl
el && el.appendChild(div)
return id
}
function setWatermark(str: string) {
createWatermark(str)
func = () => {
createWatermark(str)
}
window.addEventListener('resize', func)
}
return { setWatermark, clear }
}

View File

@ -0,0 +1,39 @@
import { XTableProps } from '@/components/XTable/src/type'
export interface tableMethod {
reload: () => void // 刷新表格
setProps: (props: XTableProps) => void
deleteData: (id: string | number) => void // 删除数据
deleteBatch: () => void // 批量删除
exportList: (fileName?: string) => void // 导出列表
getCurrentColumn: () => void // 获取当前列
getRadioRecord: () => void // 获取当前选中列radio
getCheckboxRecords: () => void //获取当前选中列, checkbox
}
export const useXTable = (props: XTableProps): [Function, tableMethod] => {
const tableRef = ref<Nullable<tableMethod>>(null)
const register = (instance) => {
tableRef.value = instance
props && instance.setProps(props)
}
const getInstance = (): tableMethod => {
const table = unref(tableRef)
if (!table) {
console.error('表格实例不存在')
}
return table as tableMethod
}
const methods: tableMethod = {
reload: () => getInstance().reload(),
setProps: (props) => getInstance().setProps(props),
deleteData: (id: string | number) => getInstance().deleteData(id),
deleteBatch: () => getInstance().deleteBatch(),
exportList: (fileName?: string) => getInstance().exportList(fileName),
getCurrentColumn: () => getInstance().getCheckboxRecords(),
getRadioRecord: () => getInstance().getRadioRecord(),
getCheckboxRecords: () => getInstance().getCheckboxRecords()
}
return [register, methods]
}