init
This commit is contained in:
89
src/hooks/design/useAvueUpload.ts
Normal file
89
src/hooks/design/useAvueUpload.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { downloadByUrl } from '@/utils/filt';
|
||||
export default function (jsEnhanceObj: Ref<any>) {
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
//文件大小格式化
|
||||
const fileSizeFormatter = (fileSize) => {
|
||||
const unitArr = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
fileSize = parseFloat(fileSize)
|
||||
const index = Math.floor(Math.log(fileSize) / Math.log(1024))
|
||||
fileSize = fileSize / Math.pow(1024, index)
|
||||
//保留的小数位数
|
||||
if (`${fileSize}`.indexOf('.') != -1) fileSize = fileSize.toFixed(2)
|
||||
return fileSize + ' ' + unitArr[index]
|
||||
}
|
||||
|
||||
//校验文件类型
|
||||
const verifyFileType = (fileName) => {
|
||||
const imgExp = /\.(gif|jpg|jpeg|png|webp|svg|GIF|JPG|JPEG|PNG|WEBP|SVG)/
|
||||
const videoExp = /\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|ogg|mp4)/
|
||||
const audioExp = /\.(mp3|wav|MP3|WAV)/
|
||||
if (imgExp.test(fileName)) return 'image/*'
|
||||
if (videoExp.test(fileName)) return 'video/*'
|
||||
if (audioExp.test(fileName)) return 'audio/*'
|
||||
return false
|
||||
}
|
||||
|
||||
const uploadBefore = async (file, done, loading, column) => {
|
||||
let bool = false
|
||||
if (column.controlType == 'image') {
|
||||
if (column.accept == 'image/*' && verifyFileType(file.name) == column.accept) bool = true
|
||||
else if (column.accept) {
|
||||
const accept = column.accept instanceof Array ? column.accept : column.accept.split(',')
|
||||
if (accept.includes(file.type)) bool = true
|
||||
} else bool = true
|
||||
}
|
||||
if (column.controlType == 'file') {
|
||||
if (column.accept) {
|
||||
const nameList = file.name.split('.')
|
||||
const suffix = `.${nameList[nameList.length - 1]}`
|
||||
const accept = column.accept instanceof Array ? column.accept : column.accept.split(',')
|
||||
|
||||
accept.forEach(type => {
|
||||
if (['image/*', 'video/*', 'audio/*'].includes(type) && verifyFileType(file.name) == type) bool = true
|
||||
})
|
||||
if (accept.includes(suffix) || accept.includes(file.type)) bool = true
|
||||
} else bool = true
|
||||
}
|
||||
try {
|
||||
if (column.verify) {
|
||||
bool = await column.verify(file).then(() => true).catch(() => false)
|
||||
}
|
||||
} catch (error) { }
|
||||
if (!bool) {
|
||||
message.info(`请上传正确的${column.label}格式`)
|
||||
loading()
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (jsEnhanceObj.value.beforeUpload) {
|
||||
const isUpload = await jsEnhanceObj.value.beforeUpload(file)
|
||||
if (!isUpload) {
|
||||
loading()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`'js增强【beforeUpload】方法执行异常,请检查'
|
||||
${error}`)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
const uploadExceed = (limit, files, fileList, column) => {
|
||||
message.info(`${column.label} 最大可上传 ${limit}${column.controlType == 'image' ? '张' : '件'}`)
|
||||
}
|
||||
const uploadSized = (fileSize, files, fileList, column) => {
|
||||
fileSize = fileSizeFormatter(fileSize)
|
||||
message.info(`${column.label} 上传大小不可超过 ${fileSize}`)
|
||||
}
|
||||
const uploadPreview = (file, column, done) => {
|
||||
if (column.controlType == 'image') return done()
|
||||
const bool = verifyFileType(file.url)
|
||||
if (bool) done()
|
||||
else downloadByUrl({ url: file.url })
|
||||
}
|
||||
|
||||
return { uploadBefore, uploadExceed, uploadSized, uploadPreview }
|
||||
}
|
||||
28
src/hooks/design/useCopyText.ts
Normal file
28
src/hooks/design/useCopyText.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
export default function () {
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const copy = async (text: string) => {
|
||||
if (navigator.clipboard) {
|
||||
const { copy, copied, isSupported } = useClipboard({ source: text })
|
||||
if (!isSupported) {
|
||||
message.error(t('common.copyError'))
|
||||
return
|
||||
}
|
||||
await copy()
|
||||
if (unref(copied)) {
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
message.success(t('common.copySuccess'));
|
||||
}
|
||||
}
|
||||
return { copyText: copy }
|
||||
}
|
||||
26
src/hooks/design/useCrudHeight.ts
Normal file
26
src/hooks/design/useCrudHeight.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
export const useCrudHeight = (crudRef) => {
|
||||
const windowSize = useWindowSize()
|
||||
const crudHeightTimer = ref<any>(null)
|
||||
|
||||
const initTableLayout = () => {
|
||||
if (crudHeightTimer.value) clearTimeout(crudHeightTimer.value)
|
||||
crudHeightTimer.value = setTimeout(() => {
|
||||
if (crudRef instanceof Array) {
|
||||
crudRef.forEach(itemRef => {
|
||||
if (itemRef.value) itemRef.value.getTableHeight()
|
||||
})
|
||||
} else if (crudRef.value) crudRef.value.getTableHeight()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => windowSize.height.value,
|
||||
() => {
|
||||
initTableLayout()
|
||||
}
|
||||
)
|
||||
|
||||
return { initTableLayout, windowSize }
|
||||
}
|
||||
24
src/hooks/design/useCrudPermi.ts
Normal file
24
src/hooks/design/useCrudPermi.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
|
||||
export const useCrudPermi = () => {
|
||||
const { wsCache } = useCache()
|
||||
const all_permission = '*:*'
|
||||
const permissions = wsCache.get(CACHE_KEY.USER).permissions
|
||||
const crudBtnObj = {
|
||||
query: 'viewBtn',
|
||||
create: 'addBtn',
|
||||
update: 'editBtn',
|
||||
delete: 'delBtn',
|
||||
}
|
||||
|
||||
const getCurrPermi = (permiArr: string[]) => {
|
||||
const crudPermission = {}
|
||||
permiArr.forEach(permiKey => {
|
||||
for (const key in crudBtnObj) {
|
||||
crudPermission[crudBtnObj[key]] = permiKey === all_permission || permissions[`${permiKey}:${key}`]
|
||||
}
|
||||
})
|
||||
return crudPermission
|
||||
}
|
||||
return { getCurrPermi }
|
||||
}
|
||||
78
src/hooks/design/useDrageed.ts
Normal file
78
src/hooks/design/useDrageed.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
export default function () {
|
||||
const onMove = (e) => {
|
||||
const type = e.draggedContext.element.type
|
||||
const toClassName = e.to.className.split(' ')
|
||||
// console.log(type, toClassName)
|
||||
if (
|
||||
type == 'layoutGroup' &&
|
||||
['layout-group__body', 'layout-table__body'].includes(toClassName[0])
|
||||
) {
|
||||
//禁止 group 拖拽进 group\table
|
||||
return false
|
||||
}
|
||||
if (type == 'layoutTable' && ['layout-table__body'].includes(toClassName[0])) {
|
||||
//禁止 table 拖拽进 table
|
||||
return false
|
||||
}
|
||||
if (
|
||||
type == 'layoutTabs' &&
|
||||
['layout-tabs__body', 'layout-table__body'].includes(toClassName[0])
|
||||
) {
|
||||
//禁止 tabs 拖拽进 tabs/table
|
||||
return false
|
||||
}
|
||||
if (type == 'layoutTabs' && ['tabs-layout-group__body'].includes(toClassName[3])) {
|
||||
//禁止 tabs 拖拽进 tabs内的group
|
||||
return false
|
||||
}
|
||||
if (type == 'comboBox' && ['layout-table__body'].includes(toClassName[0])) {
|
||||
//禁止 comboBox 拖拽进 table
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
['ueditor', 'buttonList', 'title'].includes(type) &&
|
||||
['layout-table__body'].includes(toClassName[0])
|
||||
) {
|
||||
//禁止 富文本、按钮组、文本 拖拽进 table
|
||||
return false
|
||||
}
|
||||
|
||||
// 限制组合框可拖拽控件
|
||||
if (['combo-box__body'].includes(toClassName[0])) {
|
||||
if (['input', 'select', 'date', 'time'].includes(e.draggedContext.element.controlType)) {
|
||||
if (['textarea', 'radio', 'checkbox', 'switch'].includes(type)) return false
|
||||
} else {
|
||||
if (!['buttonList'].includes(type)) return false
|
||||
}
|
||||
if (type == 'comboBox') return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const handleDragPosition = (newIndex, columnData) => {
|
||||
let isGroup = false
|
||||
if (columnData[newIndex]) isGroup = columnData[newIndex].type == 'layoutGroup'
|
||||
let repIndex: number | undefined = undefined
|
||||
columnData.forEach((item, index) => {
|
||||
if (repIndex === undefined && item.type == 'layoutGroup') {
|
||||
if (isGroup && index > newIndex) repIndex = index != 0 ? index - 1 : index
|
||||
else if (!isGroup && index <= newIndex) repIndex = index
|
||||
}
|
||||
})
|
||||
if (isGroup && repIndex !== newIndex) {
|
||||
const column = columnData.splice(newIndex, 1)
|
||||
if (repIndex === undefined) repIndex = columnData.length
|
||||
if (column[0]) columnData.splice(repIndex, 0, column[0])
|
||||
} else if (!isGroup && repIndex !== undefined && repIndex < newIndex) {
|
||||
const column = columnData.splice(newIndex, 1)
|
||||
if (column[0]) columnData.splice(repIndex, 0, column[0])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
onMove,
|
||||
handleDragPosition
|
||||
}
|
||||
}
|
||||
117
src/hooks/design/useGroup.ts
Normal file
117
src/hooks/design/useGroup.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { listToTree, findNode, treeMap } from '@/utils/tree'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
export const useGroup = (treeRef, DataApi, resetChange, isView?, isOneLevel?) => {
|
||||
const message = useMessage() // 消息弹窗
|
||||
const treeForm = ref<any>({})
|
||||
const treeOption = ref({
|
||||
nodeKey: 'id',
|
||||
defaultExpandAll: true,
|
||||
filterText: '输入名称进行过滤',
|
||||
props: { label: 'name', value: 'id' },
|
||||
formOption: {
|
||||
labelWidth: 100,
|
||||
column: {
|
||||
pid: { label: '上级分组', type: 'tree', value: 0, disabled: isOneLevel, dicData: [], filterable: true, defaultExpandAll: true, props: { label: 'name', value: 'id' } },
|
||||
name: { label: '分组名称', rules: [{ required: true, message: '请输入 分组名称', trigger: "blur" }] }
|
||||
}
|
||||
}
|
||||
})
|
||||
if (isOneLevel) treeOption.value.formOption['filterParams'] = ['pid']
|
||||
const treeData = ref<any>([])
|
||||
const groupValue = ref<string | number>(0)
|
||||
const currMenuNodeData = ref<any>({})
|
||||
|
||||
if (isView) {
|
||||
treeOption.value['addBtn'] = false
|
||||
treeOption.value['editBtn'] = false
|
||||
treeOption.value['delBtn'] = false
|
||||
treeOption.value['menu'] = false
|
||||
}
|
||||
|
||||
const treePermission = (key, data) => {
|
||||
if (key != 'addBtn' && data.id === 0) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const treeNodeContextmenu = (data) => {
|
||||
currMenuNodeData.value = data
|
||||
}
|
||||
|
||||
const treeBeforeOpen = (done, type) => {
|
||||
setTimeout(() => {
|
||||
const treeList = cloneDeep(treeData.value)
|
||||
if (type == 'edit') {
|
||||
const disabledArr = [treeForm.value.id]
|
||||
treeMap(treeList, {
|
||||
children: 'children',
|
||||
conversion: (item) => {
|
||||
if (item.id == disabledArr[0]) item.disabled = true
|
||||
if (disabledArr.includes(item.pid)) {
|
||||
item.disabled = true
|
||||
disabledArr.push(item.id)
|
||||
}
|
||||
return item
|
||||
}
|
||||
})
|
||||
treeForm.value.oldPid = treeForm.value.pid
|
||||
} else {
|
||||
treeForm.value.pid = currMenuNodeData.value.id
|
||||
}
|
||||
treeOption.value.formOption.column.pid.dicData = treeList
|
||||
if (isOneLevel) treeForm.value.pid = 0
|
||||
}, 30)
|
||||
done()
|
||||
}
|
||||
|
||||
const treeNodeClick = (data) => {
|
||||
if (data.id == groupValue.value) {
|
||||
treeRef.value.setCurrentKey(null)
|
||||
groupValue.value = ''
|
||||
} else groupValue.value = data.id
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const getTreeData = async () => {
|
||||
const data = await DataApi.getGroupData({})
|
||||
treeData.value = [{ name: '全部', id: 0, children: listToTree(data) }]
|
||||
}
|
||||
|
||||
const treeUpdate = (node, data, done, loading) => {
|
||||
DataApi.updateGroupData(data)
|
||||
.then(() => {
|
||||
if (data.oldPid != data.pid) {
|
||||
const oldPNode = findNode(treeData.value, (node) => node.id == data.oldPid)
|
||||
oldPNode.children = oldPNode.children.filter((item) => item.id != data.id)
|
||||
const pNode = findNode(treeData.value, (node) => node.id == data.pid)
|
||||
delete data.oldPid
|
||||
if (pNode.children) pNode.children.push(data)
|
||||
else pNode.children = [data]
|
||||
}
|
||||
done()
|
||||
})
|
||||
.catch(() => loading())
|
||||
}
|
||||
const treeSave = async (node, data, done, loading) => {
|
||||
treeForm.value['children'] = []
|
||||
await DataApi.saveGroupData(data)
|
||||
.then((res) => {
|
||||
treeForm.value.id = res
|
||||
done()
|
||||
setTimeout(() => {
|
||||
if (treeData.value.length > 1) {
|
||||
const currData = treeData.value.splice(1, 1)
|
||||
const pNode = findNode(treeData.value, (node) => node.id == currData[0].pid)
|
||||
if (pNode) pNode.children.push(currData[0])
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
.catch(() => loading())
|
||||
}
|
||||
const treeDel = async (node, done) => {
|
||||
await message.delConfirm()
|
||||
await DataApi.deleteGroupData([node.data.id])
|
||||
done()
|
||||
}
|
||||
|
||||
return { treeForm, treeOption, treeData, groupValue, treePermission, treeNodeContextmenu, treeBeforeOpen, treeNodeClick, getTreeData, treeUpdate, treeSave, treeDel }
|
||||
}
|
||||
57
src/hooks/design/useMEDialog.ts
Normal file
57
src/hooks/design/useMEDialog.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export default function () {
|
||||
interface MEDialog {
|
||||
value: boolean
|
||||
title?: string
|
||||
params?: object
|
||||
otherParams?: object
|
||||
handleClose?: any
|
||||
}
|
||||
interface MEData {
|
||||
value: string
|
||||
language?: string
|
||||
editorOption?: object
|
||||
params?: object
|
||||
setFormValue?: (value: string) => void
|
||||
}
|
||||
|
||||
const MEDialog = ref<MEDialog>({ value: false })
|
||||
const MEData = ref<MEData>({ value: '' })
|
||||
const openMEDialog = (column, tableForm) => {
|
||||
const { prop, label, params } = column
|
||||
const dialogParams = {}
|
||||
const meParams = {}
|
||||
let otherParams = {}
|
||||
if (typeof params == 'object') {
|
||||
for (const key in params) {
|
||||
if (['title', 'width', 'fullscreen', 'headerBtn', 'footerBtn', 'dialogParams'].includes(key)) dialogParams[key] = params[key]
|
||||
else if (['language', 'editorOption', 'providerType', 'oldValue'].includes(key)) meParams[key] = params[key]
|
||||
if (key == 'otherParams') otherParams = params[key]
|
||||
}
|
||||
}
|
||||
dialogParams['handleClose'] = (done) => {
|
||||
if (MEData.value.setFormValue) MEData.value.setFormValue(MEData.value.value)
|
||||
if (params && params.handleClose) params.handleClose(done)
|
||||
else done()
|
||||
}
|
||||
MEDialog.value = {
|
||||
value: true,
|
||||
params: {
|
||||
destroyOnClose:true,
|
||||
title: label,
|
||||
...dialogParams,
|
||||
},
|
||||
otherParams: Object.keys(otherParams).length ? otherParams : false
|
||||
}
|
||||
MEData.value = {
|
||||
value: prop ? tableForm[prop] : tableForm || '',
|
||||
params: meParams,
|
||||
setFormValue: (value: string) => {
|
||||
if (tableForm && prop) tableForm[prop] = value
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
MEDialog, MEData, openMEDialog
|
||||
}
|
||||
}
|
||||
341
src/hooks/design/useMonacoEditor.ts
Normal file
341
src/hooks/design/useMonacoEditor.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import * as monaco from 'monaco-editor'
|
||||
import { ref, nextTick, onBeforeUnmount } from 'vue'
|
||||
//语言
|
||||
import 'monaco-editor/esm/vs/basic-languages/scss/scss.contribution';
|
||||
import 'monaco-editor/esm/vs/basic-languages/java/java.contribution';
|
||||
import * as MySql from 'monaco-editor/esm/vs/basic-languages/mysql/mysql.js';
|
||||
import * as JavaScript from 'monaco-editor/esm/vs/basic-languages/javascript/javascript.js';
|
||||
import * as Java from 'monaco-editor/esm/vs/basic-languages/java/java.js';
|
||||
// 查找控件
|
||||
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController';
|
||||
import enhanceTip from '@/components/LowDesign/src/utils/enhanceTip';
|
||||
|
||||
import * as sqlFormatter from 'sql-formatter'
|
||||
|
||||
interface completions {
|
||||
label: string
|
||||
insertText: string
|
||||
detail?: string
|
||||
kind?: any
|
||||
sortText?: string
|
||||
}
|
||||
|
||||
const monacoProviderRef = ref<any>({})
|
||||
const providerType = ref('')
|
||||
let disposeArr: any[] = []
|
||||
//清除提示
|
||||
function clearProvider() {
|
||||
for (const key in monacoProviderRef.value) monacoProviderRef.value[key]?.dispose()
|
||||
}
|
||||
function initLanguageProvider() {
|
||||
clearProvider()
|
||||
const sqlProvider: any = {
|
||||
provideCompletionItems: (model, position) => {
|
||||
const suggestions: completions[] = []
|
||||
const { lineNumber, column } = position
|
||||
const textBeforePointer = model.getValueInRange({
|
||||
startLineNumber: lineNumber,
|
||||
startColumn: 0,
|
||||
endLineNumber: lineNumber,
|
||||
endColumn: column,
|
||||
})
|
||||
const contents = textBeforePointer.trim().split(/\s+/)
|
||||
const lastContents = contents[contents?.length - 1] // 获取最后一段非空字符串
|
||||
if (lastContents) {
|
||||
const sqlConfigKey = ['builtinFunctions', 'keywords', 'operators']
|
||||
sqlConfigKey.forEach(key => {
|
||||
MySql.language[key].forEach(sql => suggestions.push({ label: sql, insertText: sql, kind: monaco.languages.CompletionItemKind.Value }))
|
||||
})
|
||||
}
|
||||
return { suggestions }
|
||||
}
|
||||
}
|
||||
const javaProvider: any = {
|
||||
provideCompletionItems: (model, position) => {
|
||||
const suggestions: completions[] = []
|
||||
const { lineNumber, column } = position
|
||||
const textBeforePointer = model.getValueInRange({
|
||||
startLineNumber: lineNumber,
|
||||
startColumn: 0,
|
||||
endLineNumber: lineNumber,
|
||||
endColumn: column,
|
||||
})
|
||||
const contents = textBeforePointer.trim().split(/\s+/)
|
||||
const lastContents = contents[contents?.length - 1] // 获取最后一段非空字符串
|
||||
if (lastContents) {
|
||||
const javaConfigKey = ['keywords', 'operators']
|
||||
javaConfigKey.forEach(key => {
|
||||
Java.language[key].forEach(java => suggestions.push({ label: java, insertText: java, kind: monaco.languages.CompletionItemKind.Value }))
|
||||
})
|
||||
}
|
||||
return { suggestions }
|
||||
}
|
||||
}
|
||||
|
||||
let javaScriptDesign: any = []
|
||||
const { tipList, triggerObj } = enhanceTip[providerType.value] || {}
|
||||
if (tipList) javaScriptDesign = tipList
|
||||
const javaScriptProvider: any = {
|
||||
provideCompletionItems: (model, position) => {
|
||||
if (!providerType.value) return { suggestions: [] }
|
||||
const suggestions: completions[] = []
|
||||
const { lineNumber, column } = position
|
||||
const textBeforePointer = model.getValueInRange({
|
||||
startLineNumber: lineNumber,
|
||||
startColumn: 0,
|
||||
endLineNumber: lineNumber,
|
||||
endColumn: column,
|
||||
})
|
||||
const contents = textBeforePointer.trim().split(/\s+/)
|
||||
const lastContents = contents[contents?.length - 1] // 获取最后一段非空字符串
|
||||
const setTipFun = (bool) => {
|
||||
javaScriptDesign.forEach((javaScript) => {
|
||||
const item = { ...javaScript, sortText: bool ? '100' : '' }
|
||||
suggestions.push(item)
|
||||
})
|
||||
}
|
||||
let triggerKey = ''
|
||||
if (triggerObj && lastContents) {
|
||||
const lastLeng = lastContents.length
|
||||
for (const key in triggerObj) {
|
||||
if (triggerKey) break
|
||||
const findIndex = lastContents.lastIndexOf(key)
|
||||
if (findIndex != -1) {
|
||||
const keyLeng = key.length
|
||||
if (lastLeng - keyLeng == findIndex) triggerKey = key
|
||||
}
|
||||
}
|
||||
}
|
||||
if (triggerKey) {
|
||||
javaScriptDesign = triggerObj[triggerKey]
|
||||
setTipFun(true)
|
||||
return { incomplete: false, suggestions }
|
||||
}
|
||||
javaScriptDesign = tipList || []
|
||||
if (lastContents) {
|
||||
const javaScriptConfigKey = ['operators']
|
||||
javaScriptConfigKey.forEach(key => {
|
||||
JavaScript.language[key].forEach(javaScript => suggestions.push({ label: javaScript, insertText: javaScript }))
|
||||
})
|
||||
setTipFun(false)
|
||||
}
|
||||
return { incomplete: false, suggestions }
|
||||
},
|
||||
triggerCharacters: ['.'],
|
||||
}
|
||||
|
||||
monacoProviderRef.value.mysql = monaco.languages.registerCompletionItemProvider('mysql', sqlProvider);
|
||||
monacoProviderRef.value.java = monaco.languages.registerCompletionItemProvider('java', javaProvider);
|
||||
monacoProviderRef.value.javascript = monaco.languages.registerCompletionItemProvider('javascript', javaScriptProvider);
|
||||
}
|
||||
|
||||
function addMySqlFormat() {
|
||||
const sqlFormatDisposable = monaco.editor.addEditorAction({
|
||||
id: 'format-sql',
|
||||
label: '格式化 SQL',
|
||||
contextMenuGroupId: 'navigation',
|
||||
contextMenuOrder: 1.5,
|
||||
run: (ed) => {
|
||||
const original = ed.getValue();
|
||||
const formatted = sqlFormatter.format(original, {
|
||||
language: 'mysql',
|
||||
params: ['#\\{[^}]+\\}']
|
||||
});
|
||||
ed.setValue(formatted)
|
||||
}
|
||||
})
|
||||
const list = [...(sqlFormatDisposable['_toDispose'] || [])]
|
||||
disposeArr.push(...list)
|
||||
}
|
||||
|
||||
function emptyDispose() {
|
||||
if (disposeArr.length) {
|
||||
disposeArr.forEach(item => item.dispose && item.dispose())
|
||||
disposeArr = []
|
||||
}
|
||||
}
|
||||
|
||||
export function useMonacoEditor(language: string = 'javascript') {
|
||||
// 编辑器示例
|
||||
let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
|
||||
// 目标元素
|
||||
const monacoEditorRef = ref<HTMLElement | null>(null)
|
||||
// 创建实例
|
||||
function createEditor(editorOption: monaco.editor.IStandaloneEditorConstructionOptions = {}, type = '') {
|
||||
providerType.value = type
|
||||
if (!monacoEditorRef.value) return
|
||||
initLanguageProvider()
|
||||
if (language)
|
||||
monacoEditor = monaco.editor.create(monacoEditorRef.value, {
|
||||
// 初始模型
|
||||
model: monaco.editor.createModel('', language),
|
||||
|
||||
minimap: { enabled: true },
|
||||
// 圆角
|
||||
roundedSelection: true,
|
||||
// 主题
|
||||
theme: 'vs-dark',
|
||||
multiCursorModifier: 'ctrlCmd',
|
||||
// 滚动条
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 8,
|
||||
horizontalScrollbarSize: 8
|
||||
},
|
||||
// 行号
|
||||
lineNumbers: 'on',
|
||||
// tab大小
|
||||
tabSize: 2,
|
||||
//字体大小
|
||||
fontSize: 14,
|
||||
// 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
|
||||
autoIndent: 'advanced',
|
||||
autoClosingBrackets: 'always',//补全括号
|
||||
autoClosingQuotes: 'always', //补全冒号
|
||||
// 自动布局
|
||||
automaticLayout: true,
|
||||
fixedOverflowWidgets: true,
|
||||
...editorOption,
|
||||
})
|
||||
return monacoEditor
|
||||
}
|
||||
emptyDispose()
|
||||
if (language == 'mysql') addMySqlFormat()
|
||||
// 格式化
|
||||
async function formatDoc() {
|
||||
await monacoEditor?.getAction('editor.action.formatDocument')?.run()
|
||||
}
|
||||
|
||||
// 数据更新
|
||||
function updateVal(val: string) {
|
||||
nextTick(() => {
|
||||
monacoEditor?.setValue(val)
|
||||
setTimeout(async () => {
|
||||
await formatDoc()
|
||||
}, 10)
|
||||
})
|
||||
}
|
||||
|
||||
// 配置更新
|
||||
function updateOptions(opt: monaco.editor.IStandaloneEditorConstructionOptions, type = '') {
|
||||
providerType.value = type
|
||||
initLanguageProvider()
|
||||
monacoEditor?.updateOptions(opt)
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
function getOption(name: monaco.editor.EditorOption) {
|
||||
return monacoEditor?.getOption(name)
|
||||
}
|
||||
|
||||
// 获取实例
|
||||
function getEditor() {
|
||||
return monacoEditor
|
||||
}
|
||||
|
||||
// 设置语言
|
||||
function setLanguage(language, type = '') {
|
||||
providerType.value = type
|
||||
const text = monacoEditor?.getModel()?.getValue() || ''
|
||||
const model = monaco.editor.createModel(text, language)
|
||||
monacoEditor?.setModel(model)
|
||||
emptyDispose()
|
||||
if (language == 'mysql') addMySqlFormat()
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (monacoEditor) {
|
||||
clearProvider()
|
||||
emptyDispose()
|
||||
monacoEditor.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
monacoEditorRef,
|
||||
createEditor,
|
||||
getEditor,
|
||||
setLanguage,
|
||||
updateVal,
|
||||
updateOptions,
|
||||
getOption,
|
||||
formatDoc,
|
||||
}
|
||||
}
|
||||
export function useDiffEditor(language: string = 'javascript', newValue, oldValue) {
|
||||
// 编辑器示例
|
||||
let diffEditor: monaco.editor.IStandaloneDiffEditor | null = null
|
||||
let originalModel: any = null
|
||||
let modifiedModel: any = null
|
||||
// 目标元素
|
||||
const deffEditorRef = ref<HTMLElement | null>(null)
|
||||
// 创建实例
|
||||
function createDeffEditor(editorOption: monaco.editor.IStandaloneDiffEditorConstructionOptions = {}, type = '') {
|
||||
providerType.value = type
|
||||
if (!deffEditorRef.value) return
|
||||
initLanguageProvider()
|
||||
diffEditor = monaco.editor.createDiffEditor(deffEditorRef.value, {
|
||||
fontSize: 14, // 字体大小
|
||||
theme: 'vs-dark', //主题
|
||||
readOnly: false, // 是否只读
|
||||
overviewRulerBorder: false, // 滚动是否有边框
|
||||
cursorSmoothCaretAnimation: 'off', // 控制光标平滑动画的开启与关闭。当开启时,光标移动会有平滑的动画效果。
|
||||
mouseWheelZoom: true, //设置是否开启鼠标滚轮缩放功能
|
||||
folding: true, //控制是否开启代码折叠功能
|
||||
automaticLayout: true, // 控制编辑器是否自动调整布局以适应容器大小的变化
|
||||
// 是否启用预览图
|
||||
minimap: { enabled: true },
|
||||
// 滚动条
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 8,
|
||||
horizontalScrollbarSize: 8
|
||||
},
|
||||
wordWrap: "off", // 关闭自动换行
|
||||
scrollBeyondLastLine: false,
|
||||
roundedSelection: true, // 右侧不显示编辑器预览框
|
||||
originalEditable: false, // 是否允许修改原始文本
|
||||
...editorOption,
|
||||
})
|
||||
originalModel = monaco.editor.createModel(oldValue, language);
|
||||
modifiedModel = monaco.editor.createModel(newValue, language);
|
||||
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
||||
return { diffEditor, originalModel, modifiedModel }
|
||||
}
|
||||
//获取实例
|
||||
function getEditor(type) {
|
||||
if (type == 'diff') return diffEditor
|
||||
if (type == 'original') return originalModel
|
||||
if (type == 'modified') return modifiedModel
|
||||
}
|
||||
// 格式化
|
||||
async function formatDoc() {
|
||||
await originalModel?.getAction('editor.action.formatDocument')?.run()
|
||||
await modifiedModel?.getAction('editor.action.formatDocument')?.run()
|
||||
}
|
||||
// 数据更新
|
||||
function updateVal(val: string, type) {
|
||||
nextTick(() => {
|
||||
if (type == 'original') return originalModel?.setValue(val)
|
||||
if (type == 'modified') return modifiedModel?.setValue(val)
|
||||
setTimeout(async () => {
|
||||
await formatDoc()
|
||||
}, 10)
|
||||
})
|
||||
}
|
||||
// 设置语言
|
||||
function setLanguage(language, type = '') {
|
||||
providerType.value = type
|
||||
const originalText = originalModel?.getValue() || ''
|
||||
const modifiedText = modifiedModel?.getValue() || ''
|
||||
originalModel = monaco.editor.createModel(originalText, language)
|
||||
modifiedModel = monaco.editor.createModel(modifiedText, language)
|
||||
diffEditor?.setModel({ original: originalModel, modified: modifiedModel })
|
||||
}
|
||||
|
||||
return {
|
||||
deffEditorRef,
|
||||
createDeffEditor,
|
||||
getEditor,
|
||||
updateVal,
|
||||
setLanguage
|
||||
}
|
||||
}
|
||||
60
src/hooks/event/useScrollTo.ts
Normal file
60
src/hooks/event/useScrollTo.ts
Normal 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 }
|
||||
}
|
||||
4
src/hooks/login/loginApi.ts
Normal file
4
src/hooks/login/loginApi.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function isValidPhoneNumber(phoneNumber: string): boolean {
|
||||
const regex = /^1[3-9]\d{9}$/;
|
||||
return regex.test(phoneNumber);
|
||||
}
|
||||
30
src/hooks/web/useCache.ts
Normal file
30
src/hooks/web/useCache.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 配置浏览器本地存储的方式,可直接存储对象数组。
|
||||
*/
|
||||
|
||||
import WebStorageCache from 'web-storage-cache'
|
||||
|
||||
type CacheType = 'localStorage' | 'sessionStorage'
|
||||
|
||||
export const CACHE_KEY = {
|
||||
IS_DARK: 'isDark',
|
||||
DARK_BEFORE_COLOR:'darkBeforeColor',
|
||||
USER: 'user',
|
||||
LANG: 'lang',
|
||||
THEME: 'theme',
|
||||
LAYOUT: 'layout',
|
||||
LOW_REGION: 'lowRegion',
|
||||
ROLE_ROUTERS: 'roleRouters',
|
||||
DICT_CACHE: 'dictCache',
|
||||
FULLSCREEN:'fullscreen'
|
||||
}
|
||||
|
||||
export const useCache = (type: CacheType = 'localStorage') => {
|
||||
const wsCache: WebStorageCache = new WebStorageCache({
|
||||
storage: type
|
||||
})
|
||||
|
||||
return {
|
||||
wsCache
|
||||
}
|
||||
}
|
||||
9
src/hooks/web/useConfigGlobal.ts
Normal file
9
src/hooks/web/useConfigGlobal.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ConfigGlobalTypes } from '@/types/configGlobal'
|
||||
|
||||
export const useConfigGlobal = () => {
|
||||
const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes
|
||||
|
||||
return {
|
||||
configGlobal
|
||||
}
|
||||
}
|
||||
18
src/hooks/web/useDesign.ts
Normal file
18
src/hooks/web/useDesign.ts
Normal 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
22
src/hooks/web/useEmitt.ts
Normal 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
|
||||
}
|
||||
}
|
||||
49
src/hooks/web/useGuide.ts
Normal file
49
src/hooks/web/useGuide.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Config, driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
export const useGuide = (options?: Config) => {
|
||||
const driverObj = driver(
|
||||
options || {
|
||||
showProgress: true,
|
||||
nextBtnText: t('common.nextLabel'),
|
||||
prevBtnText: t('common.prevLabel'),
|
||||
doneBtnText: t('common.doneLabel'),
|
||||
steps: [
|
||||
{
|
||||
element: `#${variables.namespace}-menu`,
|
||||
popover: {
|
||||
title: t('common.menu'),
|
||||
description: t('common.menuDes'),
|
||||
side: 'right'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tool-header`,
|
||||
popover: {
|
||||
title: t('common.tool'),
|
||||
description: t('common.toolDes'),
|
||||
side: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tags-view`,
|
||||
popover: {
|
||||
title: t('common.tagsView'),
|
||||
description: t('common.tagsViewDes'),
|
||||
side: 'bottom'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
...driverObj
|
||||
}
|
||||
}
|
||||
56
src/hooks/web/useI18n.ts
Normal file
56
src/hooks/web/useI18n.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { i18n } from '@/plugins/vueI18n'
|
||||
import type { Composer } from 'vue-i18n'
|
||||
|
||||
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
|
||||
mergeLocaleMessage: Composer['mergeLocaleMessage']
|
||||
getLocaleMessage: Composer['getLocaleMessage']
|
||||
} => {
|
||||
const normalFn = {
|
||||
t: (key: string) => {
|
||||
return getKey(namespace, key)
|
||||
},
|
||||
} as Composer
|
||||
|
||||
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
|
||||
//@ts-ignore
|
||||
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
8
src/hooks/web/useIcon.ts
Normal 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)
|
||||
}
|
||||
35
src/hooks/web/useLocale.ts
Normal file
35
src/hooks/web/useLocale.ts
Normal 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
|
||||
}
|
||||
}
|
||||
97
src/hooks/web/useMessage.ts
Normal file
97
src/hooks/web/useMessage.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
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, tip?: string, config: object = {}) {
|
||||
ElMessageBox.alert(content, tip ? tip : t('common.confirmTitle'), config)
|
||||
},
|
||||
// 错误提示
|
||||
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, config: object = {}) {
|
||||
return ElMessageBox.confirm(content, tip ? tip : t('common.confirmTitle'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning',
|
||||
...config,
|
||||
})
|
||||
},
|
||||
// 删除窗体
|
||||
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, config: object = {}) {
|
||||
return ElMessageBox.prompt(content, tip, {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning',
|
||||
...config,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/hooks/web/useNProgress.ts
Normal file
33
src/hooks/web/useNProgress.ts
Normal 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
|
||||
}
|
||||
}
|
||||
21
src/hooks/web/useNetwork.ts
Normal file
21
src/hooks/web/useNetwork.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
const useNetwork = () => {
|
||||
const online = ref(true)
|
||||
|
||||
const updateNetwork = () => {
|
||||
online.value = navigator.onLine
|
||||
}
|
||||
|
||||
window.addEventListener('online', updateNetwork)
|
||||
window.addEventListener('offline', updateNetwork)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('online', updateNetwork)
|
||||
window.removeEventListener('offline', updateNetwork)
|
||||
})
|
||||
|
||||
return { online }
|
||||
}
|
||||
|
||||
export { useNetwork }
|
||||
60
src/hooks/web/useNow.ts
Normal file
60
src/hooks/web/useNow.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { reactive, toRefs } from 'vue'
|
||||
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'
|
||||
|
||||
export const useNow = (immediate = true) => {
|
||||
let timer: IntervalHandle
|
||||
|
||||
const state = reactive({
|
||||
year: 0,
|
||||
month: 0,
|
||||
week: '',
|
||||
day: 0,
|
||||
hour: '',
|
||||
minute: '',
|
||||
second: 0,
|
||||
meridiem: ''
|
||||
})
|
||||
|
||||
const update = () => {
|
||||
const now = dayjs()
|
||||
|
||||
const h = now.format('HH')
|
||||
const m = now.format('mm')
|
||||
const s = now.get('s')
|
||||
|
||||
state.year = now.get('y')
|
||||
state.month = now.get('M') + 1
|
||||
state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]
|
||||
state.day = now.get('date')
|
||||
state.hour = h
|
||||
state.minute = m
|
||||
state.second = s
|
||||
|
||||
state.meridiem = now.format('A')
|
||||
}
|
||||
|
||||
function start() {
|
||||
update()
|
||||
clearInterval(timer)
|
||||
timer = setInterval(() => update(), 1000)
|
||||
}
|
||||
|
||||
function stop() {
|
||||
clearInterval(timer)
|
||||
}
|
||||
|
||||
tryOnMounted(() => {
|
||||
immediate && start()
|
||||
})
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
stop()
|
||||
})
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
start,
|
||||
stop
|
||||
}
|
||||
}
|
||||
20
src/hooks/web/usePageLoading.ts
Normal file
20
src/hooks/web/usePageLoading.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
|
||||
|
||||
|
||||
export const usePageLoading = () => {
|
||||
const loadStart = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
appStore.setPageLoading(true)
|
||||
}
|
||||
|
||||
const loadDone = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
appStore.setPageLoading(false)
|
||||
}
|
||||
|
||||
return {
|
||||
loadStart,
|
||||
loadDone
|
||||
}
|
||||
}
|
||||
63
src/hooks/web/useTagsView.ts
Normal file
63
src/hooks/web/useTagsView.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
|
||||
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
|
||||
import { computed, nextTick, unref } from 'vue'
|
||||
|
||||
export const useTagsView = () => {
|
||||
const tagsViewStore = useTagsViewStoreWithOut()
|
||||
|
||||
const { replace, currentRoute } = useRouter()
|
||||
|
||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||
|
||||
const closeAll = (callback?: Fn) => {
|
||||
tagsViewStore.delAllViews()
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeLeft = (callback?: Fn) => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeRight = (callback?: Fn) => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeOther = (callback?: Fn) => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view || unref(currentRoute))
|
||||
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
tagsViewStore.delCachedView()
|
||||
const { path, query } = view || unref(currentRoute)
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
})
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const setTitle = (title: string, path?: string) => {
|
||||
tagsViewStore.setTitle(title, path)
|
||||
}
|
||||
|
||||
return {
|
||||
closeAll,
|
||||
closeLeft,
|
||||
closeRight,
|
||||
closeOther,
|
||||
closeCurrent,
|
||||
refreshPage,
|
||||
setTitle
|
||||
}
|
||||
}
|
||||
49
src/hooks/web/useTimeAgo.ts
Normal file
49
src/hooks/web/useTimeAgo.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core'
|
||||
import { useLocaleStoreWithOut } from '@/store/modules/locale'
|
||||
|
||||
const TIME_AGO_MESSAGE_MAP: {
|
||||
'zh-CN': UseTimeAgoMessages
|
||||
en: UseTimeAgoMessages
|
||||
} = {
|
||||
// @ts-ignore
|
||||
'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} 秒`
|
||||
},
|
||||
// @ts-ignore
|
||||
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
|
||||
}
|
||||
25
src/hooks/web/useTitle.ts
Normal file
25
src/hooks/web/useTitle.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { watch, ref } from 'vue'
|
||||
import { isString } from '@/utils/is'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
|
||||
|
||||
export const useTitle = (newTitle?: string) => {
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
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
|
||||
}
|
||||
60
src/hooks/web/useValidator.ts
Normal file
60
src/hooks/web/useValidator.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormItemRule } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface LengthRange {
|
||||
min: number
|
||||
max: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const useValidator = () => {
|
||||
const required = (message?: string): FormItemRule => {
|
||||
return {
|
||||
required: true,
|
||||
message: message || t('common.required')
|
||||
}
|
||||
}
|
||||
|
||||
const lengthRange = (options: LengthRange): FormItemRule => {
|
||||
const { min, max, message } = options
|
||||
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
message: message || t('common.lengthRange', { min, max })
|
||||
}
|
||||
}
|
||||
|
||||
const notSpace = (message?: string): FormItemRule => {
|
||||
return {
|
||||
validator: (_, val, callback) => {
|
||||
if (val?.indexOf(' ') !== -1) {
|
||||
callback(new Error(message || t('common.notSpace')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const notSpecialCharacters = (message?: string): FormItemRule => {
|
||||
return {
|
||||
validator: (_, val, callback) => {
|
||||
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
|
||||
callback(new Error(message || t('common.notSpecialCharacters')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
required,
|
||||
lengthRange,
|
||||
notSpace,
|
||||
notSpecialCharacters
|
||||
}
|
||||
}
|
||||
55
src/hooks/web/useWatermark.ts
Normal file
55
src/hooks/web/useWatermark.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user