├── .gitignore ├── CSS └── scrollbar │ ├── readme.md │ └── scrollbar.css ├── .vscode └── settings.json ├── .husky └── pre-commit ├── utils ├── saveFile.ts ├── rgbToHex.ts ├── swapElements.ts ├── clearObject.ts ├── slicePage.ts ├── browser.ts ├── readablizeBytes.ts ├── removeEventListenerByType.js ├── genRules.ts ├── types.ts ├── microphoneVolume.js ├── storage.ts ├── formatPast.ts └── prettyLog.ts ├── .gitmodules ├── wangeditor ├── index.d.ts ├── toolbar.ts └── index.vue ├── inputNumberRange ├── readme.md └── index.vue ├── betterScroll ├── readme.md └── index.vue ├── message ├── antd.ts └── index.ts ├── useLabel └── index.ts ├── useSortable └── index.ts ├── usePassTime ├── index.ts └── demo.html ├── .eslintrc.js ├── uploadImage ├── uploadFn.ts ├── useUploadImage.ts └── uploadImage.vue ├── useList ├── types.ts ├── other.ts ├── index.test.ts ├── data.json └── index.ts ├── LICENSE ├── canvas └── wrapText.js ├── uni-request └── index.ts ├── useSendCode └── index.ts ├── package.json ├── fetchUtil ├── request.ts ├── readme.md └── fetch.ts ├── dynamicTags ├── index.vue └── useDynamicTags.ts ├── directive └── auth.ts ├── readme.md ├── stroage └── chrome.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /CSS/scrollbar/readme.md: -------------------------------------------------------------------------------- 1 | ## 滚动条样式 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "echarts", 4 | "wangeditor" 5 | ] 6 | } -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "正在校验文件... 请不要关闭进程" 5 | pnpm lint 6 | -------------------------------------------------------------------------------- /utils/saveFile.ts: -------------------------------------------------------------------------------- 1 | export default function saveFile(url: string) { 2 | const a = document.createElement('a'); 3 | a.href = url; 4 | a.click(); 5 | } 6 | -------------------------------------------------------------------------------- /utils/rgbToHex.ts: -------------------------------------------------------------------------------- 1 | export function rgbToHex(r: number, g: number, b: number) { 2 | return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`; 3 | } -------------------------------------------------------------------------------- /utils/swapElements.ts: -------------------------------------------------------------------------------- 1 | export default function swapElements(arr: any[], index1: number, index2: number): any[] { 2 | if (arr.length < index1 || arr.length < index2) return arr; 3 | [arr[index1], arr[index2]] = [arr[index2], arr[index1]]; 4 | return arr; 5 | } 6 | -------------------------------------------------------------------------------- /utils/clearObject.ts: -------------------------------------------------------------------------------- 1 | export default (obj: any, value = undefined) :any => { 2 | if (!obj) return {}; 3 | const keys = Reflect.ownKeys(obj); 4 | const tmp = {}; 5 | keys.forEach((key) => { 6 | Reflect.set(tmp, key, value); 7 | }); 8 | return tmp; 9 | }; 10 | -------------------------------------------------------------------------------- /utils/slicePage.ts: -------------------------------------------------------------------------------- 1 | export default function slicePage(data: T[], page = 1, limit = 10) { 2 | const start = (page - 1) * limit; 3 | const end = start + limit; 4 | return { 5 | data: data.slice(start, end), 6 | total: Math.ceil(data.length / limit), 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "axios-bz"] 2 | path = axios-bz 3 | url = https://github.com/QC2168/axios-bz 4 | [submodule "useCharts"] 5 | path = useCharts 6 | url = https://github.com/QC2168/useCharts 7 | [submodule "useDialog"] 8 | path = useDialog 9 | url = https://github.com/QC2168/useDialog 10 | -------------------------------------------------------------------------------- /utils/browser.ts: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/element-plus/element-plus/blob/dev/packages/utils/browser.ts 2 | 3 | import { isClient, isIOS } from '@vueuse/core'; 4 | 5 | export const isFirefox = (): boolean => isClient && /firefox/i.test(window.navigator.userAgent); 6 | 7 | export { isClient, isIOS }; 8 | -------------------------------------------------------------------------------- /utils/readablizeBytes.ts: -------------------------------------------------------------------------------- 1 | // 文件大小后缀转换 2 | export default function readablizeBytes(bytes: number): string { 3 | if (bytes === 0) return ''; 4 | const s = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; 5 | const e = Math.floor(Math.log(bytes) / Math.log(1024)); 6 | return `${(bytes / 1024 ** Math.floor(e)).toFixed(2)} ${s[e]}` ?? 0; 7 | } 8 | -------------------------------------------------------------------------------- /wangeditor/index.d.ts: -------------------------------------------------------------------------------- 1 | import { SlateDescendant } from '@wangeditor/editor'; 2 | 3 | declare module '@wangeditor/editor' { 4 | // 扩展 Text 5 | interface SlateText { 6 | text: string 7 | } 8 | 9 | // 扩展 Element 10 | interface SlateElement { 11 | type: string 12 | children: SlateDescendant[] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inputNumberRange/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## e.g. 3 | ```vue 4 | 11 | 17 | ``` -------------------------------------------------------------------------------- /betterScroll/readme.md: -------------------------------------------------------------------------------- 1 | ## Demo 2 | 3 | ```vue 4 | 9 | 13 | 20 | ``` 21 | -------------------------------------------------------------------------------- /message/antd.ts: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | export function createSuccessMessage(msg: string) { 4 | message.success(msg); 5 | } 6 | 7 | export function createWarningMessage(msg: string) { 8 | message.warning(msg); 9 | } 10 | 11 | export function createErrorMessage(msg: string) { 12 | message.error(msg); 13 | } 14 | 15 | export function createInfoMessage(msg: string) { 16 | message.info(msg); 17 | } 18 | -------------------------------------------------------------------------------- /CSS/scrollbar/scrollbar.css: -------------------------------------------------------------------------------- 1 | /* 滚动槽 */ 2 | ::-webkit-scrollbar { 3 | width: 6px; 4 | height: 6px; 5 | } 6 | ::-webkit-scrollbar-track { 7 | border-radius: 3px; 8 | background: rgba(0,0,0,0.06); 9 | -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.08); 10 | } 11 | /* 滚动条滑块 */ 12 | ::-webkit-scrollbar-thumb { 13 | border-radius: 3px; 14 | background: rgba(0,0,0,0.12); 15 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.2); 16 | } -------------------------------------------------------------------------------- /utils/removeEventListenerByType.js: -------------------------------------------------------------------------------- 1 | function removeEventListenerByType(element, eventType) { 2 | // 获取元素上的所有事件监听器 3 | const listeners = getEventListeners(element)?.[eventType]; 4 | 5 | if (listeners) { 6 | // 遍历所有的监听器并移除它们 7 | listeners.forEach((listenerInfo) => { 8 | // 获取事件监听器的实际函数 9 | const { listener } = listenerInfo; 10 | 11 | // 移除事件监听器 12 | element.removeEventListener(eventType, listener); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /useLabel/index.ts: -------------------------------------------------------------------------------- 1 | import { CascaderOption } from 'element-plus'; 2 | import { ref, onMounted } from 'vue'; 3 | 4 | export default function useLabel(requestApi:()=>Promise) { 5 | const list = ref([]); 6 | const load = async () => { 7 | const data = await requestApi(); 8 | list.value = data; 9 | }; 10 | onMounted(async () => { 11 | await load(); 12 | }); 13 | return { 14 | list, 15 | load, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /message/index.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage, MessageOptions } from 'element-plus'; 2 | 3 | export function message(message: string, option?:MessageOptions) { 4 | ElMessage({ message, ...option }); 5 | } 6 | export function warningMessage(message: string, option?:MessageOptions) { 7 | ElMessage({ message, ...option, type: 'warning' }); 8 | } 9 | export function errorMessage(message: string, option?:MessageOptions) { 10 | ElMessage({ message, ...option, type: 'error' }); 11 | } 12 | export function infoMessage(message: string, option?:MessageOptions) { 13 | ElMessage({ message, ...option, type: 'info' }); 14 | } 15 | -------------------------------------------------------------------------------- /useSortable/index.ts: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/vbenjs/vue-vben-admin/blob/main/src/hooks/web/useSortable.ts 2 | 3 | import { nextTick, unref } from 'vue'; 4 | import type { Ref } from 'vue'; 5 | 6 | export default function useSortable(el: HTMLElement | Ref, options?: any) { 7 | function initSortable() { 8 | nextTick(async () => { 9 | if (!el) return; 10 | 11 | const Sortable = (await import('sortablejs')).default; 12 | Sortable.create(unref(el), { 13 | animation: 500, 14 | delay: 400, 15 | delayOnTouchOnly: true, 16 | ...options, 17 | }); 18 | }); 19 | } 20 | 21 | return { initSortable }; 22 | } 23 | -------------------------------------------------------------------------------- /usePassTime/index.ts: -------------------------------------------------------------------------------- 1 | export default function usePassTime(sourceTime:number) { 2 | const currentTime = Date.now(); 3 | const time = currentTime - sourceTime; 4 | const day = Math.floor(time / (1000 * 60 * 60 * 24)); 5 | const hour = Math.floor(time / (1000 * 60 * 60)); 6 | const min = Math.floor(time / (1000 * 60)); 7 | const second = Math.floor(time / 1000); 8 | const month = Math.floor(day / 30); 9 | const year = Math.floor(month / 12); 10 | if (year) return `${year}年前`; 11 | if (month) return `${month}个月前`; 12 | if (day) return `${day}天前`; 13 | if (hour) return `${hour}小时前`; 14 | if (min) return `${min}分钟前`; 15 | if (second) return `${second}秒前`; 16 | return '刚刚'; 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | 'eslint:recommended', 10 | 'plugin:vue/vue3-recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | overrides: [ 14 | ], 15 | parser: 'vue-eslint-parser', 16 | parserOptions: { 17 | parser: '@typescript-eslint/parser', 18 | ecmaVersion: 'latest', 19 | }, 20 | plugins: [ 21 | '@typescript-eslint', 22 | ], 23 | rules: { 24 | '@typescript-eslint/no-explicit-any': 'off', 25 | 'no-shadow': 'off', 26 | 'import/extensions': 'off', 27 | 'import/no-unresolved': 'off', 28 | 'vue/multi-word-component-names': 'off', 29 | 'no-underscore-dangle': 'off', 30 | 'max-len': 'off', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /uploadImage/uploadFn.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { UploadRequestOptions } from 'element-plus'; 3 | 4 | // 图片上传接口,根据项目页面自行调整 5 | export default ( 6 | cb?: (percent: number) => void, 7 | signal?: AbortSignal, 8 | ) => { 9 | let _signal = signal; 10 | return { 11 | req: ({ file }: UploadRequestOptions) => axios.request({ 12 | url: '/upload', 13 | method: 'post', 14 | data: { 15 | file, 16 | }, 17 | signal: _signal, 18 | headers: { 19 | 'Content-Type': 'multipart/form-data', 20 | }, 21 | onUploadProgress: (progressEvent) => { 22 | const percent = ((progressEvent.loaded / (progressEvent.total as number)) * 100) || 0; 23 | cb?.(percent); 24 | }, 25 | timeout: 1000 * 60 * 2, 26 | }), 27 | updateSignal: (signal: AbortSignal) => { 28 | _signal = signal; 29 | }, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /useList/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue'; 2 | // Api接口类型 3 | export interface ResponseDataType { 4 | data: T; 5 | meta: { total: number }; 6 | } 7 | 8 | export interface ExportLinkType { 9 | link: string; 10 | } 11 | 12 | export interface MessageType { 13 | GET_DATA_IF_FAILED?: string; 14 | GET_DATA_IF_SUCCEED?: string; 15 | EXPORT_DATA_IF_FAILED?: string; 16 | EXPORT_DATA_IF_SUCCEED?: string; 17 | } 18 | 19 | export interface ListReturn { 20 | data: T[]; 21 | total: number; 22 | } 23 | export interface OptionsType { 24 | requestError?: () => void; 25 | requestSuccess?: () => void; 26 | exportError?: () => void; 27 | exportSuccess?: () => void; 28 | filterOption?: Ref; 29 | transformFn?: (...args:any[]) => ListReturn; 30 | exportRequestFn?: (...args: any) => Promise>; 31 | message?: MessageType; 32 | preRequest?: (...args:any[]) => void; 33 | immediate?: boolean; 34 | } 35 | -------------------------------------------------------------------------------- /utils/genRules.ts: -------------------------------------------------------------------------------- 1 | import { type RuleItem } from 'async-validator'; 2 | 3 | interface genRequiredRuleOption { 4 | min: number; 5 | max: number; 6 | required: boolean; 7 | type: string; 8 | pattern: string | RegExp; 9 | } 10 | export const genRequiredRule = ( 11 | message: string, 12 | opt?: Partial, 13 | ): RuleItem => { 14 | const { 15 | min, max, pattern, type = 'string', required = true, 16 | } = opt || {}; 17 | return { 18 | required, 19 | message, 20 | pattern, 21 | type, 22 | min, 23 | max, 24 | trigger: ['blur', 'change'], 25 | transform(value: string) { 26 | if (value === null || value === undefined) return ''; 27 | return value.trim(); 28 | }, 29 | } as RuleItem; 30 | }; 31 | 32 | interface genRulesType { 33 | key: string | symbol; 34 | message: string; 35 | rules: RuleItem[]; 36 | } 37 | 38 | export const genRules = ({ 39 | key, 40 | message, 41 | rules, 42 | }: genRulesType): Record => ({ 43 | [key]: [genRequiredRule(message), ...rules], 44 | }); 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 山田 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /utils/types.ts: -------------------------------------------------------------------------------- 1 | // copy from 2 | import { isArray, isObject, isString } from '@vue/shared'; 3 | import { isNil } from 'lodash-unified'; 4 | 5 | export { 6 | isArray, 7 | isFunction, 8 | isObject, 9 | isString, 10 | isDate, 11 | isPromise, 12 | isSymbol, 13 | } from '@vue/shared'; 14 | 15 | export { isVNode } from 'vue'; 16 | 17 | export const isUndefined = (val: any): val is undefined => val === undefined; 18 | export const isBoolean = (val: any): val is boolean => typeof val === 'boolean'; 19 | export const isNumber = (val: any): val is number => typeof val === 'number'; 20 | 21 | export const isEmpty = (val: unknown) => (!val && val !== 0) 22 | || (isArray(val) && val.length === 0) 23 | || (isObject(val) && !Object.keys(val).length); 24 | 25 | export const isElement = (e: unknown): e is Element => { 26 | if (typeof Element === 'undefined') return false; 27 | return e instanceof Element; 28 | }; 29 | 30 | export const isPropAbsent = (prop: unknown): prop is null | undefined => isNil(prop); 31 | 32 | export const isStringNumber = (val: string): boolean => { 33 | if (!isString(val)) { 34 | return false; 35 | } 36 | return !Number.isNaN(Number(val)); 37 | }; 38 | -------------------------------------------------------------------------------- /canvas/wrapText.js: -------------------------------------------------------------------------------- 1 | // copy from https://www.zhangxinxu.com/wordpress/2018/02/canvas-text-break-line-letter-spacing-vertical/ 2 | 3 | CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) { 4 | if (typeof text !== 'string' || typeof x !== 'number' || typeof y !== 'number') { 5 | return; 6 | } 7 | 8 | const context = this; 9 | const { canvas } = context; 10 | 11 | if (typeof maxWidth === 'undefined') { 12 | maxWidth = (canvas && canvas.width) || 300; 13 | } 14 | if (typeof lineHeight === 'undefined') { 15 | lineHeight = (canvas && parseInt(window.getComputedStyle(canvas).lineHeight)) || parseInt(window.getComputedStyle(document.body).lineHeight); 16 | } 17 | 18 | // 字符分隔为数组 19 | const arrText = text.split(''); 20 | let line = ''; 21 | 22 | for (let n = 0; n < arrText.length; n++) { 23 | const testLine = line + arrText[n]; 24 | const metrics = context.measureText(testLine); 25 | const testWidth = metrics.width; 26 | if (testWidth > maxWidth && n > 0) { 27 | context.fillText(line, x, y); 28 | line = arrText[n]; 29 | y += lineHeight; 30 | } else { 31 | line = testLine; 32 | } 33 | } 34 | context.fillText(line, x, y); 35 | }; 36 | -------------------------------------------------------------------------------- /uni-request/index.ts: -------------------------------------------------------------------------------- 1 | const BASE_URL = 'API BASE URL'; 2 | 3 | export interface CommonResponseType { 4 | /** 5 | * 返回码 6 | */ 7 | code : number; 8 | /** 9 | * 返回数据 10 | */ 11 | data : T; 12 | /** 13 | * 返回消息 14 | */ 15 | message ?: string; 16 | /** 17 | * 是否成功 18 | */ 19 | success ?: boolean; 20 | } 21 | 22 | interface RequestOption { 23 | url : string; 24 | method ?: 'POST' | 'GET' | 'PUT' | 'GET' | 'CONNECT' | 'HEAD' | 'OPTIONS' | 'TRACE'; 25 | data ?: any; 26 | header ?: any; 27 | timeout ?: number; 28 | } 29 | 30 | interface ResponseType { 31 | data : T; 32 | statusCode : number; 33 | header : Record<'string', 'string'>; 34 | cookies : Array; 35 | } 36 | 37 | const request = (options : RequestOption) : Promise> => new Promise((resolve, reject) => { 38 | const { 39 | url, method = 'GET', data, header, timeout = 1000 * 5, 40 | } = options; 41 | uni.request({ 42 | url: BASE_URL + url, 43 | method, 44 | data, 45 | timeout, 46 | header, 47 | success: (res : ResponseType>) => { 48 | resolve(res.data); 49 | }, 50 | fail: (err : Error) => { 51 | reject(err); 52 | }, 53 | }); 54 | }); 55 | 56 | // export 57 | export default request; 58 | -------------------------------------------------------------------------------- /useSendCode/index.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | 3 | export default function useSendCode(cb: (mobile: string) => any, time = 60) { 4 | // 是否发送验证码 5 | const isSendCode = ref(false); 6 | // 按钮文本 7 | const btnText = ref('发送验证码'); 8 | // 倒计时 9 | const remainSecond = ref(time); 10 | // 按钮可视 11 | const disable = computed(() => remainSecond.value !== 0); 12 | // 计时器对象 13 | const timer = ref(null); 14 | // 倒计时 15 | const countDown = () => { 16 | remainSecond.value = time; 17 | btnText.value = `已发送(${remainSecond.value}秒后可重新发送)`; 18 | timer.value = setInterval(() => { 19 | remainSecond.value -= 1; 20 | btnText.value = `已发送(${remainSecond.value}秒后可重新发送)`; 21 | // 计数0时取消计时器 22 | if (remainSecond.value <= 0 && timer.value !== null) { 23 | clearInterval(timer.value); 24 | remainSecond.value = 0; 25 | isSendCode.value = false; 26 | btnText.value = '重新发送'; 27 | } 28 | }, 1000); 29 | }; 30 | 31 | // 点击发送验证码 32 | const sendCode = async (mobile: string) => { 33 | // 判断是否输入手机号 34 | // 发送请求 35 | await cb(mobile); 36 | isSendCode.value = true; 37 | // 倒计时 38 | countDown(); 39 | }; 40 | return { 41 | sendCode, 42 | btnText, 43 | disable, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /utils/microphoneVolume.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | 4 | function startVolumeDetection(stream) { 5 | const audioContext = new (window.AudioContext || window.webkitAudioContext)(); 6 | const source = audioContext.createMediaStreamSource(stream); 7 | const analyser = audioContext.createAnalyser(); 8 | analyser.fftSize = 2048; // 可以根据需要调整 9 | source.connect(analyser); 10 | 11 | const bufferLength = analyser.frequencyBinCount; 12 | const dataArray = new Uint8Array(bufferLength); 13 | 14 | // 更新音频数据 15 | function updateVolume() { 16 | analyser.getByteFrequencyData(dataArray); 17 | let sum = 0; 18 | for (let i = 0; i < bufferLength; i++) { 19 | sum += dataArray[i]; 20 | } 21 | const average = sum / bufferLength; 22 | const volumePercentage = (average / 255) * 100; 23 | // 输出音量百分比 24 | console.log(`音量百分比: ${volumePercentage.toFixed(2)}%`); 25 | } 26 | 27 | setInterval(updateVolume, 100); // 每隔100毫秒更新一次音量 28 | } 29 | 30 | function initMicrophoneVolumeDetection() { 31 | if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 32 | navigator.mediaDevices.getUserMedia({ audio: true }) 33 | .then(startVolumeDetection) 34 | .catch((err) => { 35 | console.log(`拒绝访问麦克风: ${err}`); 36 | }); 37 | } else { 38 | console.log('您的浏览器不支持Web Audio API'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snippets", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "some snippets", 6 | "main": "index.js", 7 | "author": "QC2168", 8 | "license": "MIT", 9 | "scripts": { 10 | "test": "vitest", 11 | "coverage": "vitest run --coverage", 12 | "lint": "eslint --fix --ext .js,.vue,.ts ./", 13 | "prepare": "husky install" 14 | }, 15 | "dependencies": { 16 | "@better-scroll/core": "^2.5.0", 17 | "@element-plus/icons-vue": "^2.0.10", 18 | "@types/lodash-es": "^4.17.7", 19 | "@types/node": "^18.11.9", 20 | "@types/qs": "^6.9.7", 21 | "@types/sortablejs": "^1.15.1", 22 | "@types/uni-app": "^1.4.4", 23 | "@wangeditor/editor": "^5.1.23", 24 | "async-validator": "^4.2.5", 25 | "axios": "^1.2.6", 26 | "element-plus": "^2.2.23", 27 | "eslint-config-airbnb": "^19.0.4", 28 | "eslint-plugin-import": "^2.27.5", 29 | "lodash-es": "^4.17.21", 30 | "qs": "^6.11.2", 31 | "sortablejs": "^1.15.0", 32 | "typescript": "^5.0.3", 33 | "vitest": "^0.28.4", 34 | "vue": "^3.2.45" 35 | }, 36 | "devDependencies": { 37 | "@dcloudio/types": "^3.3.3", 38 | "@types/better-scroll": "^1.12.4", 39 | "@typescript-eslint/eslint-plugin": "^5.57.1", 40 | "@typescript-eslint/parser": "^5.57.1", 41 | "eslint": "^8.37.0", 42 | "eslint-plugin-vue": "^9.11.0", 43 | "husky": "^8.0.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fetchUtil/request.ts: -------------------------------------------------------------------------------- 1 | // import { ElMessage, ElMessageBox } from 'element-plus'; 2 | // import { Session } from '../../../@/utils/storage'; 3 | // import fetchUtil from '../../../@/utils/fetch'; 4 | 5 | // // 配置新建一个实例 6 | // const service = fetchUtil.create({ 7 | // baseURL: 'http://localhost:8888/dev', 8 | // headers: { 'Content-Type': 'application/json' }, 9 | // }); 10 | 11 | // // 添加请求拦截器 12 | // service.interceptors.request.use( 13 | // (config) => { 14 | // // 在发送请求之前做些什么 token 15 | // if (Session.get('token')) { 16 | // config.headers = { 17 | // Authorization: `${Session.get('token')}`, 18 | // }; 19 | // } 20 | // return config; 21 | // }, 22 | // ); 23 | 24 | // // 添加响应拦截器 25 | // service.interceptors.response.use( 26 | // (res) => { 27 | // if (res?.code !== 200) { 28 | // // `token` 过期或者账号已在别处登录 29 | // if (res.code === 401) { 30 | // Session.clear(); // 清除浏览器全部临时缓存 31 | // window.location.href = '/'; // 去登录页 32 | // ElMessageBox.alert('你已被登出,请重新登录', '提示', {}) 33 | // .then(() => { 34 | // }) 35 | // .catch(() => { 36 | // }); 37 | // } 38 | // ElMessage.error(res.message); 39 | // return Promise.reject(res); 40 | // } 41 | // return res; 42 | // }, 43 | // (error) => { 44 | // // 对响应错误做点什么 45 | // ElMessage.error(error); 46 | // return error; 47 | // }, 48 | // ); 49 | 50 | // // 导出实例 51 | // export default service; 52 | -------------------------------------------------------------------------------- /fetchUtil/readme.md: -------------------------------------------------------------------------------- 1 | 基于fetch封装的请求工具,实现全局一个请求,使用说明如下: 2 | 3 | 1. 实例化,并传入全局配置 4 | 5 | ``` 6 | const service = fetchUtil.create({ 7 | baseURL: 'http://localhost:8888/dev', 8 | headers: {'Content-Type': 'application/json'}, 9 | }); 10 | ``` 11 | 12 | 2. 请求接口可以单独传入配置,也可传入完整请求路径,优先级更高 13 | 14 | ``` 15 | let res = await request.get('/admin/user/login', data,{ 16 | headers: {'Content-Type': 'application/json'} 17 | }); 18 | ``` 19 | 20 | 3. 如果是表单请求,直接使用URLSearchParams构造参数,使用URLSearchParams时会自动维护请求头中的Content-Type字段 21 | 22 | ``` 23 | const data=new URLSearchParams() 24 | data.append("username", "admin"); 25 | data.append("password", "123456"); 26 | const res = await useLoginApi().logIn(data); 27 | ``` 28 | 29 | 4. 如果是上传文件的请求,直接使用FormData构造参数,会自动维护请求头中的Content-Type字段 30 | 31 | ``` 32 | const data=new FormData() 33 | data.append("username", "admin"); 34 | data.append("password", "123456"); 35 | const res = await useLoginApi().logIn(data); 36 | ``` 37 | 38 | 4. 如果是提交json数据,直接传入一个普通对象即可,如果此时调用接口时传入配置项,将header中的Content-Type修改为application/x-www-form-urlencoded,即可自动转换为表单请求 39 | 40 | ``` 41 | const res = await useLoginApi().logIn({ 42 | username:"admin", 43 | password:"123456" 44 | }); 45 | ``` 46 | 47 | 5. 如果是下载文件,或者其他非json格式的响应数据,需要配置responseType字段,如果全部接口都是下载文件,直接全局配置,如果是个别接口用作下载文件,单独在接口处配置 48 | 49 | ``` 50 | let res = await request.get('/admin/user/login', data,{ 51 | responseType:"blob" 52 | }); 53 | ``` 54 | 55 | request.ts文件中提高了全局配置和拦截器的用法 -------------------------------------------------------------------------------- /usePassTime/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

现在距离过去已经秒了

9 |

现在距离2023年1月1日0:00已经过去

10 | 11 | 39 | -------------------------------------------------------------------------------- /useList/other.ts: -------------------------------------------------------------------------------- 1 | import response from './data.json'; 2 | 3 | /** 4 | * ProfileBody 5 | */ 6 | export interface ProfileBody { 7 | /** 8 | * Avatar,用户头像地址 9 | */ 10 | avatar?: string; 11 | /** 12 | * Birthday 13 | */ 14 | birthday?: string; 15 | /** 16 | * Date Join 17 | */ 18 | dateJoin?: string; 19 | /** 20 | * Email 21 | */ 22 | email?: string; 23 | /** 24 | * Gender 25 | */ 26 | gender?: number; 27 | /** 28 | * Group 29 | */ 30 | group?: string; 31 | /** 32 | * Id 33 | */ 34 | id?: number; 35 | /** 36 | * Invitation Code 37 | */ 38 | invitationCode?: string; 39 | /** 40 | * Is Active 41 | */ 42 | isActive?: boolean; 43 | /** 44 | * Last Login 45 | */ 46 | lastLogin?: string; 47 | /** 48 | * Mobile 49 | */ 50 | mobile?: string; 51 | /** 52 | * National Name,证件上的名字(真实名字) 53 | */ 54 | nationalName?: string; 55 | /** 56 | * Nickname,用户呢称 57 | */ 58 | nickname?: string; 59 | /** 60 | * Residential Region,用户所属居住地区(中文字符) 61 | */ 62 | residentialRegion?: string; 63 | /** 64 | * User 65 | */ 66 | user?: string; 67 | } 68 | 69 | export interface ResponseType { 70 | data: T; 71 | meta: { 72 | total: number; 73 | }; 74 | } 75 | export function requestFn(): Promise> { 76 | return new Promise((resolve) => { 77 | resolve(response); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /utils/storage.ts: -------------------------------------------------------------------------------- 1 | // copy from https://gitee.com/lyt-top/vue-next-admin/blob/master/src/utils/storage.ts 2 | 3 | /* 4 | 键前缀 5 | */ 6 | 7 | const PREFIX = 'ProjectNameV1'; 8 | 9 | function setKey(key: string) { 10 | return `${PREFIX}:${key}`; 11 | } 12 | 13 | /** 14 | * window.localStorage 浏览器永久缓存 15 | * @method set 设置永久缓存 16 | * @method get 获取永久缓存 17 | * @method remove 移除永久缓存 18 | * @method clear 移除全部永久缓存 19 | */ 20 | export const Local = { 21 | // 设置永久缓存 22 | set(key: string, val: T) { 23 | window.localStorage.setItem(setKey(key), JSON.stringify(val)); 24 | }, 25 | // 获取永久缓存 26 | get(key: string) { 27 | const json = window.localStorage.getItem(setKey(key)); 28 | return JSON.parse(json); 29 | }, 30 | // 移除永久缓存 31 | remove(key: string) { 32 | window.localStorage.removeItem(setKey(key)); 33 | }, 34 | // 移除全部永久缓存 35 | clear() { 36 | window.localStorage.clear(); 37 | }, 38 | }; 39 | 40 | /** 41 | * window.sessionStorage 浏览器临时缓存 42 | * @method set 设置临时缓存 43 | * @method get 获取临时缓存 44 | * @method remove 移除临时缓存 45 | * @method clear 移除全部临时缓存 46 | */ 47 | export const Session = { 48 | // 设置临时缓存 49 | set(key: string, val: T) { 50 | window.sessionStorage.setItem(setKey(key), JSON.stringify(val)); 51 | }, 52 | // 获取临时缓存 53 | get(key: string) { 54 | const json = window.sessionStorage.getItem(setKey(key)); 55 | return JSON.parse(json); 56 | }, 57 | // 移除临时缓存 58 | remove(key: string) { 59 | window.sessionStorage.removeItem(setKey(key)); 60 | }, 61 | // 移除全部临时缓存 62 | clear() { 63 | window.sessionStorage.clear(); 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /useList/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { ref } from 'vue'; 3 | import useList from '.'; 4 | import response from './data.json'; 5 | import { requestFn } from './other'; 6 | 7 | describe('useList', () => { 8 | test('should have list of response.data and total of 85', async () => { 9 | const { list, total, loadData } = useList(requestFn); 10 | await loadData(); 11 | expect(list.value).toEqual(response.data); 12 | expect(total.value).toEqual(85); 13 | }); 14 | 15 | test("should have filterOption value of {name:'foo'}", async () => { 16 | const filterOption = ref({}); 17 | const { loadData } = useList(requestFn, { 18 | filterOption, 19 | preRequest: () => { 20 | filterOption.value = { name: 'foo' }; 21 | }, 22 | }); 23 | await loadData(); 24 | expect(filterOption.value).toEqual({ name: 'foo' }); 25 | }); 26 | 27 | test('should have filterOption value of empty', async () => { 28 | const filterOption = ref({ 29 | name: 'foo', 30 | mobile: '186xxxxxxxx', 31 | id: '1', 32 | }); 33 | const { loadData, reset } = useList(requestFn, { 34 | filterOption, 35 | }); 36 | await loadData(); 37 | reset(); 38 | expect(filterOption.value).toEqual({}); 39 | }); 40 | 41 | test('should have list value of empty array and total of 0', async () => { 42 | const { list, total } = useList(requestFn, { 43 | transformFn() { 44 | return { 45 | data: [], 46 | total: 0, 47 | }; 48 | }, 49 | }); 50 | expect(list.value).toEqual([]); 51 | expect(total.value).toEqual(0); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /dynamicTags/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 71 | 72 | -------------------------------------------------------------------------------- /directive/auth.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | 3 | /** 4 | * 用户权限指令 5 | * @directive 单个权限验证v-auth="xxx" 6 | * @directive 多个权限验证,满足一个则显示 v-auths="[xxx,xxx]" 7 | * @directive 多个权限验证,全部满足则显示 v-auth-all="[xxx,xxx]" 8 | */ 9 | export default function setupAuthDirective(app: App) { 10 | // 单个权限验证 11 | // v-auth="xxx" 12 | app.directive('auth', { 13 | mounted(el, binding) { 14 | const pmn = localStorage.getItem('pmn') as unknown as string[]; 15 | if (!pmn.some((v: string) => v === binding.value)) { el.parentNode.removeChild(el); } 16 | }, 17 | }); 18 | 19 | // 多个权限验证,满足一个则显示 20 | // v-auths="[xxx,xxx]" 21 | app.directive('auths', { 22 | mounted(el, binding) { 23 | const pmn = localStorage.getItem('pmn') as unknown as string[]; 24 | const authList = binding.value || []; 25 | let hasAuth = false; 26 | // 遍历权限列表,判断是否有一个权限通过验证 27 | for (let i = 0; i < authList.length; i += 1) { 28 | if (pmn.includes(authList[i])) { 29 | hasAuth = true; 30 | break; 31 | } 32 | } 33 | if (!hasAuth) el.parentNode.removeChild(el); 34 | }, 35 | }); 36 | 37 | // 多个权限验证,全部满足则显示 38 | // v-auth-all="[xxx,xxx]" 39 | app.directive('auth-all', { 40 | mounted(el, binding) { 41 | const pmn = localStorage.getItem('pmn') as unknown as string[]; 42 | const authList = binding.value || []; 43 | let hasAuth = true; 44 | // 遍历权限列表,判断是否有一个权限通过验证 45 | for (let i = 0; i < authList.length; i += 1) { 46 | if (!pmn.includes(authList[i])) { 47 | hasAuth = false; 48 | break; 49 | } 50 | } 51 | if (!hasAuth) el.parentNode.removeChild(el); 52 | }, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /wangeditor/toolbar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具栏配置 3 | */ 4 | export default [ 5 | 'headerSelect', 6 | 'blockquote', 7 | '|', 8 | 'bold', 9 | 'underline', 10 | 'italic', 11 | { 12 | key: 'group-more-style', 13 | title: '更多', 14 | iconSvg: 15 | '', 16 | menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'], 17 | }, 18 | 'color', 19 | 'bgColor', 20 | '|', 21 | 'fontSize', 22 | 'fontFamily', 23 | 'lineHeight', 24 | '|', 25 | 'bulletedList', 26 | 'numberedList', 27 | { 28 | key: 'group-justify', 29 | title: '对齐', 30 | iconSvg: 31 | '', 32 | menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'], 33 | }, 34 | { 35 | key: 'group-indent', 36 | title: '缩进', 37 | iconSvg: 38 | '', 39 | menuKeys: ['indent', 'delIndent'], 40 | }, 41 | '|', 42 | 'insertLink', 43 | { 44 | key: 'group-image', 45 | title: '图片', 46 | iconSvg: 47 | '', 48 | menuKeys: ['uploadImage'], 49 | }, 50 | 'insertTable', 51 | 'divider', 52 | '|', 53 | 'undo', 54 | 'redo', 55 | '|', 56 | 'fullScreen', 57 | ]; 58 | -------------------------------------------------------------------------------- /uploadImage/useUploadImage.ts: -------------------------------------------------------------------------------- 1 | import { UploadProps, UploadRequestOptions } from 'element-plus'; 2 | import { ref } from 'vue'; 3 | import { error } from 'console'; 4 | import { message, errorMessage } from '../message'; 5 | 6 | // 根据需求,自行扩展 7 | export type apiType = ( 8 | cb?: (percent: number) => void, 9 | signal?: AbortSignal 10 | ) => { 11 | req: (opt:UploadRequestOptions) => Promise; 12 | updateSignal: (signal: AbortSignal) => void; 13 | }; 14 | 15 | export default function useUploadImage(api: apiType) { 16 | // 控制上传 17 | const controller = ref(new AbortController()); 18 | // 上传百分比 19 | const percent = ref(0); 20 | // 加载态 21 | const isLoading = ref(false); 22 | // 图片地址 23 | const image = ref(''); 24 | 25 | const uploadProgress = (p: number) => { 26 | isLoading.value = true; 27 | percent.value = p; 28 | if (p >= 100) { 29 | message('图片发送上传成功'); 30 | } 31 | }; 32 | 33 | const uploadImgFn = api(uploadProgress, controller.value.signal); 34 | 35 | const handleSuccess: UploadProps['onSuccess'] = (response) => { 36 | percent.value = 0; 37 | isLoading.value = false; 38 | // 请根据后端返回的数据进行处理,将对应的图片地址存放到imageRef中 39 | image.value = response; 40 | }; 41 | // 终止上传图片 42 | const abort = () => { 43 | controller.value.abort(); 44 | isLoading.value = false; 45 | percent.value = 0; 46 | controller.value = new AbortController(); 47 | uploadImgFn.updateSignal(controller.value.signal); 48 | }; 49 | const uploadErrorFn = (err: Error) => { 50 | error(err); 51 | errorMessage('图片上传失败'); 52 | isLoading.value = false; 53 | }; 54 | 55 | // 上传前钩子 56 | const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { 57 | if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') { 58 | errorMessage('图片必须是JPG/PNG格式'); 59 | return false; 60 | } 61 | if (rawFile.size > 1024 * 1000 * 2) { 62 | errorMessage('图片大小不能超过2MB'); 63 | return false; 64 | } 65 | uploadImgFn.updateSignal(controller.value.signal); 66 | isLoading.value = true; 67 | return true; 68 | }; 69 | 70 | return { 71 | uploadImgFn, 72 | percent, 73 | image, 74 | isLoading, 75 | uploadErrorFn, 76 | beforeUpload, 77 | handleSuccess, 78 | abort, 79 | controller, 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 代码片段 2 | 3 | 在日常开发中,往往少不了对一些第三方库进行二次封装,达到更佳的使用效果。 4 | 5 | 为了节省开发时找封装代码片段和开销效率(摸🐟时间++) 6 | 7 | 我创建了该仓库 方便大家直接拿去到项目中使用。 8 | 9 | ## 列表 10 | 11 | | 仓库目录 | 目录内容说明 | 12 | |---------------|----------------------------------------------| 13 | | [axios-bz](https://github.com/QC2168/axios-bz) | 基于axios+typescript二次封装的库,可直接在项目中使用 | 14 | | betterScroll | 在betterScroll库的基础上搭配Vue封装的滚动组件 | 15 | | CSS/scrollbar | 美化项目中的滚动条,我的博客中的滚动条是使用了这里面的样式 | 16 | | message | 基于element-plus组件库二次封装消息提示组件 (也有ant-design的~) | 17 | | [useCharts](https://github.com/QC2168/useCharts) | 基于echarts二次封装的hooks,使用起来更加简单高效 | 18 | | [useDialog](https://github.com/QC2168/useDialog) | 基于element-plus二次封装的hooks,使用起来更加简单高效 | 19 | | useList | 基于Vue封装的快速渲染出列表数据hooks | 20 | | usePassTime | 计算当前和过去某个时间的差值 | 21 | | useSendCode | 基于Vue CompositionApi封装的发送二维码hooks | 22 | | wangeditor | 基于wangeditor富文本编辑器封装的Vue组件 | 23 | | inputNumberRange | 基于ElementUI+vue封装的范围输入组件 | 24 | | uploadImage | 基于ElementUI+vue封装的图片上传组件 | 25 | | dynamicTags | 基于ElementUI+vue封装的动态编辑标签组件 | 26 | | utils/genRules | 快速生成默认验证规则(async-validator & ElementUI) | 27 | | utils/clearObject | 快速清空对象属性值(支持自定义属性值) | 28 | | fetchUtil | 基于Typescript封装的fetchApi请求工具 | 29 | | wrapText | Canvas换行工具函数 | 30 | 31 | ## 我也想提交代码片段 32 | 33 | > 如果您也有`代码片段`想与大家一起分享,可以提交拉取请求哦!😆😆 34 | > 35 | > 非常欢迎您对该代码库做出贡献! 36 | 37 | 38 | ## 相关文章 39 | 40 | - [Vue3这样子结合hook写弹窗组件更快更高效](https://juejin.cn/post/7175821416237891644) - [useDialog](https://github.com/QC2168/useDialog) 41 | - [在Vue3这样子写页面更快更高效](https://juejin.cn/post/7172889961446768670) - [useList](https://github.com/QC2168/snippets/tree/main/useList) 42 | - [如何在Vue3中更优雅的使用echart图表](https://juejin.cn/post/7098646141889151006) - [useCharts](https://github.com/QC2168/useCharts) 43 | - [axios封装思想+API集中管理+无感刷新Token](https://juejin.cn/post/7055171070311006215) - [axios-bz](https://github.com/QC2168/axios-bz) 44 | 45 | 46 | 47 | > 如果这个代码库对您有所帮助,不妨给它点一个免费的`Star`来支持一下作者吧! 48 | > 49 | > 这不仅可以让作者感到莫大的鼓舞,还能为自己在未来更快地找到这个项目! 50 | > 51 | > 同时也会让其他人更容易地发现这个仓库。谢谢您的支持! -------------------------------------------------------------------------------- /useList/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "avatar": "http://dummyimage.com/100x100", 5 | "group": "elit nulla Excepteur", 6 | "is_active": false, 7 | "nickname": "彭杰", 8 | "gender": 28 9 | }, 10 | { 11 | "avatar": "http://dummyimage.com/100x100", 12 | "group": "elit nulla Excepteur", 13 | "is_active": false, 14 | "nickname": "彭杰", 15 | "gender": 28 16 | }, 17 | { 18 | "avatar": "http://dummyimage.com/100x100", 19 | "group": "elit nulla Excepteur", 20 | "is_active": false, 21 | "nickname": "彭杰", 22 | "gender": 28 23 | }, 24 | { 25 | "avatar": "http://dummyimage.com/100x100", 26 | "group": "elit nulla Excepteur", 27 | "is_active": false, 28 | "nickname": "彭杰", 29 | "gender": 28 30 | }, 31 | { 32 | "avatar": "http://dummyimage.com/100x100", 33 | "group": "elit nulla Excepteur", 34 | "is_active": false, 35 | "nickname": "彭杰", 36 | "gender": 28 37 | }, 38 | { 39 | "avatar": "http://dummyimage.com/100x100", 40 | "group": "elit nulla Excepteur", 41 | "is_active": false, 42 | "nickname": "彭杰", 43 | "gender": 28 44 | }, 45 | { 46 | "avatar": "http://dummyimage.com/100x100", 47 | "group": "elit nulla Excepteur", 48 | "is_active": false, 49 | "nickname": "彭杰", 50 | "gender": 28 51 | }, 52 | { 53 | "avatar": "http://dummyimage.com/100x100", 54 | "group": "elit nulla Excepteur", 55 | "is_active": false, 56 | "nickname": "彭杰", 57 | "gender": 28 58 | }, 59 | { 60 | "avatar": "http://dummyimage.com/100x100", 61 | "group": "elit nulla Excepteur", 62 | "is_active": false, 63 | "nickname": "彭杰", 64 | "gender": 28 65 | }, 66 | { 67 | "avatar": "http://dummyimage.com/100x100", 68 | "group": "elit nulla Excepteur", 69 | "is_active": false, 70 | "nickname": "彭杰", 71 | "gender": 28 72 | } 73 | ], 74 | "meta": { 75 | "total": 85 76 | } 77 | } -------------------------------------------------------------------------------- /dynamicTags/useDynamicTags.ts: -------------------------------------------------------------------------------- 1 | import { ref, nextTick, unref } from 'vue'; 2 | import { pullAt } from 'lodash-es'; 3 | import { error } from 'console'; 4 | import { warningMessage, errorMessage } from '../message'; 5 | 6 | export interface OptionsType { 7 | requestFn?: (name:string)=>any; 8 | submitFn?: (tags:string[])=>any; 9 | } 10 | 11 | export default function useDynamicTags(opt: OptionsType) { 12 | const { requestFn, submitFn } = opt; 13 | 14 | // 当前标签 15 | const tags = ref([]); 16 | // 是否显示输入框 17 | const inputTagVisible = ref(false); 18 | // 输入框元素 19 | const InputTagRef = ref(); 20 | // 输入框值 21 | const inputTagValue = ref(''); 22 | // 提交标签 23 | const submitTags = () => { 24 | if (typeof submitFn === 'function') { 25 | try { 26 | submitFn(unref(tags)); 27 | } catch { 28 | error('执行submitFn出错'); 29 | } 30 | } 31 | }; 32 | // 移除标签 33 | const handleCloseTag = (item: string) => { 34 | if (!tags.value) return; 35 | const idx = tags.value.findIndex((i) => item === i); 36 | pullAt(tags.value, idx); 37 | submitTags(); 38 | }; 39 | // 添加标签 40 | const handleInputConfirm = () => { 41 | const val = inputTagValue.value.trim(); 42 | if (val) { 43 | if (tags.value && Array.isArray(tags.value)) { 44 | // 检测重复 45 | if (tags.value.includes(val)) { 46 | warningMessage('重复的标签添加'); 47 | } else { 48 | tags.value.push(val); 49 | } 50 | } else { 51 | tags.value = [val]; 52 | } 53 | // 提交 54 | submitTags(); 55 | } 56 | inputTagVisible.value = false; 57 | }; 58 | 59 | // 显示输入框 60 | const handleAddTags = () => { 61 | inputTagValue.value = ''; 62 | inputTagVisible.value = true; 63 | nextTick(() => { 64 | InputTagRef.value?.focus(); 65 | }); 66 | }; 67 | 68 | // 查询数据 69 | const querySearchAsync = async (queryString: string, cb: any) => { 70 | if (typeof requestFn !== 'function') { 71 | cb([]); 72 | return; 73 | } 74 | try { 75 | const { data } = await requestFn(queryString); 76 | cb(data); 77 | } catch (error) { 78 | errorMessage('搜索失败'); 79 | } 80 | }; 81 | const handleInputSelect = () => { 82 | handleInputConfirm(); 83 | }; 84 | 85 | return { 86 | tags, 87 | inputTagVisible, 88 | inputTagValue, 89 | handleCloseTag, 90 | querySearchAsync, 91 | handleInputSelect, 92 | handleInputConfirm, 93 | handleAddTags, 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /utils/formatPast.ts: -------------------------------------------------------------------------------- 1 | function formatPast(date: string, type = 'default', zeroFillFlag = true): string | undefined { 2 | // 定义countTime变量,用于存储计算后的数据 3 | let countTime; 4 | // 获取当前时间戳 5 | let time = new Date().getTime(); 6 | // 转换传入参数为时间戳 7 | const afferentTime = new Date(date).getTime(); 8 | // 当前时间戳 - 传入时间戳 9 | time = Number.parseInt(`${time - afferentTime}`, 10); 10 | if (time < 10000) { 11 | // 10秒内 12 | return '刚刚'; 13 | } if (time < 60000) { 14 | // 超过10秒少于1分钟内 15 | countTime = Math.floor(time / 1000); 16 | return `${countTime}秒前`; 17 | } if (time < 3600000) { 18 | // 超过1分钟少于1小时 19 | countTime = Math.floor(time / 60000); 20 | return `${countTime}分钟前`; 21 | } if (time < 86400000) { 22 | // 超过1小时少于24小时 23 | countTime = Math.floor(time / 3600000); 24 | return `${countTime}小时前`; 25 | } if (time >= 86400000 && type === 'default') { 26 | // 超过二十四小时(一天)且格式参数为默认"default" 27 | countTime = Math.floor(time / 86400000); 28 | // 大于等于365天 29 | if (countTime >= 365) { 30 | return `${Math.floor(countTime / 365)}年前`; 31 | } 32 | // 大于等于30天 33 | if (countTime >= 30) { 34 | return `${Math.floor(countTime / 30)}个月前`; 35 | } 36 | return `${countTime}天前`; 37 | } 38 | // 一天(24小时)以上且格式不为"default"则按传入格式参数显示不同格式 39 | // 数字补零 40 | const Y = new Date(date).getFullYear(); 41 | const M = new Date(date).getMonth() + 1; 42 | const zeroFillM = M > 9 ? M : `0${M}`; 43 | const D = new Date(date).getDate(); 44 | const zeroFillD = D > 9 ? D : `0${D}`; 45 | // 传入格式为"-" "/" "." 46 | if (type === '-' || type === '/' || type === '.') { 47 | return zeroFillFlag 48 | ? Y + type + zeroFillM + type + zeroFillD 49 | : Y + type + M + type + D; 50 | } 51 | // 传入格式为"年月日" 52 | if (type === '年月日') { 53 | return zeroFillFlag 54 | ? Y + type[0] + zeroFillM + type[1] + zeroFillD + type[2] 55 | : Y + type[0] + M + type[1] + D + type[2]; 56 | } 57 | // 传入格式为"月日" 58 | if (type === '月日') { 59 | return zeroFillFlag 60 | ? zeroFillM + type[0] + zeroFillD + type[1] 61 | : M + type[0] + D + type[1]; 62 | } 63 | // 传入格式为"年" 64 | if (type === '年') { 65 | return Y + type; 66 | } 67 | 68 | return undefined; 69 | } 70 | 71 | console.log(formatPast('2024-1-1 11:11:11')); // 3天前 72 | console.log(formatPast('2023-11-1 11:11:11')); // 2个月前 73 | console.log(formatPast('2015-07-10 21:32:01')); // 8年前 74 | console.log(formatPast('2023-02-01 09:32:01', '-', false)); // 2023-2-1 75 | console.log(formatPast('2023.12.8 19:32:01', '/')); // 2023/12/08 76 | console.log(formatPast('2023.12.8 19:32:01', '.')); // 2023.12.08 77 | console.log(formatPast('2023/5/10 11:32:01', '年月日')); // 2023年05月10日 78 | console.log(formatPast('2023/6/25 11:32:01', '月日', false)); // 6月25日 79 | console.log(formatPast('2023/8/08 11:32:01', '年')); // 2023年 80 | -------------------------------------------------------------------------------- /wangeditor/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 105 | -------------------------------------------------------------------------------- /uploadImage/uploadImage.vue: -------------------------------------------------------------------------------- 1 | 56 | 98 | 110 | -------------------------------------------------------------------------------- /betterScroll/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /stroage/chrome.ts: -------------------------------------------------------------------------------- 1 | interface ChromeStorageArea { 2 | get(keys: string | string[] | null): Promise<{ [key: string]: any }>; 3 | set(items: { [key: string]: any }): Promise; 4 | remove(keys: string | string[]): Promise; 5 | clear(): Promise; 6 | } 7 | 8 | interface ChromeStorage { 9 | local: ChromeStorageArea; 10 | sync: ChromeStorageArea; 11 | } 12 | 13 | const chromeStorage: ChromeStorage = { 14 | local: { 15 | get(keys: string | string[] | null): Promise<{ [key: string]: any }> { 16 | return new Promise((resolve, reject) => { 17 | chrome.storage.local.get(keys, (result) => { 18 | if (chrome.runtime.lastError) { 19 | reject(chrome.runtime.lastError); 20 | } else { 21 | resolve(result); 22 | } 23 | }); 24 | }); 25 | }, 26 | set(items: { [key: string]: any }): Promise { 27 | return new Promise((resolve, reject) => { 28 | chrome.storage.local.set(items, () => { 29 | if (chrome.runtime.lastError) { 30 | reject(chrome.runtime.lastError); 31 | } else { 32 | resolve(); 33 | } 34 | }); 35 | }); 36 | }, 37 | remove(keys: string | string[]): Promise { 38 | return new Promise((resolve, reject) => { 39 | chrome.storage.local.remove(keys, () => { 40 | if (chrome.runtime.lastError) { 41 | reject(chrome.runtime.lastError); 42 | } else { 43 | resolve(); 44 | } 45 | }); 46 | }); 47 | }, 48 | clear(): Promise { 49 | return new Promise((resolve, reject) => { 50 | chrome.storage.local.clear(() => { 51 | if (chrome.runtime.lastError) { 52 | reject(chrome.runtime.lastError); 53 | } else { 54 | resolve(); 55 | } 56 | }); 57 | }); 58 | }, 59 | }, 60 | sync: { 61 | get(keys: string | string[] | null): Promise<{ [key: string]: any }> { 62 | return new Promise((resolve, reject) => { 63 | chrome.storage.sync.get(keys, (result) => { 64 | if (chrome.runtime.lastError) { 65 | reject(chrome.runtime.lastError); 66 | } else { 67 | resolve(result); 68 | } 69 | }); 70 | }); 71 | }, 72 | set(items: { [key: string]: any }): Promise { 73 | return new Promise((resolve, reject) => { 74 | chrome.storage.sync.set(items, () => { 75 | if (chrome.runtime.lastError) { 76 | reject(chrome.runtime.lastError); 77 | } else { 78 | resolve(); 79 | } 80 | }); 81 | }); 82 | }, 83 | remove(keys: string | string[]): Promise { 84 | return new Promise((resolve, reject) => { 85 | chrome.storage.sync.remove(keys, () => { 86 | if (chrome.runtime.lastError) { 87 | reject(chrome.runtime.lastError); 88 | } else { 89 | resolve(); 90 | } 91 | }); 92 | }); 93 | }, 94 | clear(): Promise { 95 | return new Promise((resolve, reject) => { 96 | chrome.storage.sync.clear(() => { 97 | if (chrome.runtime.lastError) { 98 | reject(chrome.runtime.lastError); 99 | } else { 100 | resolve(); 101 | } 102 | }); 103 | }); 104 | }, 105 | }, 106 | }; 107 | -------------------------------------------------------------------------------- /useList/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | onMounted, ref, unref, watch, 3 | } from 'vue'; 4 | import { errorMessage } from '../message'; 5 | import { MessageType, OptionsType, ResponseDataType } from './types'; 6 | 7 | const DEFAULT_MESSAGE: MessageType = { 8 | GET_DATA_IF_FAILED: '获取列表数据失败', 9 | EXPORT_DATA_IF_FAILED: '导出数据失败', 10 | }; 11 | 12 | export default function useList< 13 | T extends(...args: any) => Promise> 14 | >(listRequestFn: T, options: OptionsType = {}) { 15 | const { 16 | immediate = true, 17 | preRequest, 18 | message = DEFAULT_MESSAGE, 19 | filterOption = ref(), 20 | exportRequestFn = undefined, 21 | transformFn = undefined, 22 | } = options; 23 | const { GET_DATA_IF_FAILED, EXPORT_DATA_IF_FAILED } = message; 24 | // 加载态 25 | const loading = ref(false); 26 | // 当前页 27 | const curPage = ref(1); 28 | // 总 29 | const total = ref(0); 30 | // 分页大小 31 | const pageSize = ref(10); 32 | // 数据 33 | const list = ref>['data']>([]); 34 | 35 | const loadData = (page = curPage.value, size = pageSize.value) => { 36 | // 兼容page可能是event 37 | const requestPage = (typeof page === 'object') ? unref(curPage) : page; 38 | // eslint-disable-next-line no-async-promise-executor 39 | return new Promise(async (resolve) => { 40 | loading.value = true; 41 | try { 42 | preRequest?.(); 43 | const result = await listRequestFn(size, requestPage, filterOption.value); 44 | const transformResult = transformFn ? transformFn(result) : result; 45 | const { data } = transformResult; 46 | let count = 0; 47 | if ('meta' in transformResult && transformResult?.meta?.total) { 48 | count = transformResult.meta.total; 49 | } 50 | if ('total' in transformResult && transformResult.total) { 51 | count = transformResult.total; 52 | } 53 | list.value = data; 54 | total.value = count; 55 | options?.requestSuccess?.(); 56 | resolve({ 57 | list: data, 58 | total: count, 59 | }); 60 | } catch (error) { 61 | if (GET_DATA_IF_FAILED) { 62 | errorMessage(GET_DATA_IF_FAILED); 63 | } 64 | options?.requestError?.(); 65 | } finally { 66 | loading.value = false; 67 | } 68 | }); 69 | }; 70 | const reset = () => { 71 | if (!filterOption.value) return; 72 | const keys = Reflect.ownKeys(filterOption.value); 73 | keys.forEach((key) => { 74 | Reflect.set(filterOption.value, key, undefined); 75 | }); 76 | loadData(); 77 | }; 78 | 79 | const exportFile = async () => { 80 | if (!exportRequestFn && typeof exportRequestFn !== 'function') { 81 | throw new Error('当前没有提供exportRequest函数'); 82 | } 83 | try { 84 | const { 85 | data: { link }, 86 | } = await exportRequestFn(filterOption.value); 87 | window.open(link); 88 | options?.exportSuccess?.(); 89 | } catch (error) { 90 | if (EXPORT_DATA_IF_FAILED) { 91 | errorMessage(EXPORT_DATA_IF_FAILED); 92 | } 93 | options?.exportError?.(); 94 | } 95 | }; 96 | 97 | // 监听分页数据改变 98 | watch([curPage, pageSize], () => { 99 | loadData(curPage.value); 100 | }); 101 | 102 | onMounted(() => { 103 | if (immediate) { 104 | loadData(curPage.value); 105 | } 106 | }); 107 | 108 | return { 109 | loading, 110 | curPage, 111 | total, 112 | list, 113 | filterOption, 114 | reset, 115 | pageSize, 116 | exportFile, 117 | loadData, 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /utils/prettyLog.ts: -------------------------------------------------------------------------------- 1 | // copy from https://juejin.cn/post/7371716384847364147 2 | // 美化打印实现方法 3 | const prettyLog = () => { 4 | // in vite 5 | const isProduction = import.meta.env.MODE === 'production'; 6 | 7 | const isEmpty = (value: any) => value == null || value === undefined || value === ''; 8 | const prettyPrint = (title: string, text: string, color: string) => { 9 | if (isProduction) return; 10 | console.log( 11 | `%c ${title} %c ${text} %c`, 12 | `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`, 13 | `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`, 14 | 'background:transparent', 15 | ); 16 | }; 17 | const info = (textOrTitle: string, content = '') => { 18 | const title = isEmpty(content) ? 'Info' : textOrTitle; 19 | const text = isEmpty(content) ? textOrTitle : content; 20 | prettyPrint(title, text, '#909399'); 21 | }; 22 | const error = (textOrTitle: string, content = '') => { 23 | const title = isEmpty(content) ? 'Error' : textOrTitle; 24 | const text = isEmpty(content) ? textOrTitle : content; 25 | prettyPrint(title, text, '#F56C6C'); 26 | }; 27 | const warning = (textOrTitle: string, content = '') => { 28 | const title = isEmpty(content) ? 'Warning' : textOrTitle; 29 | const text = isEmpty(content) ? textOrTitle : content; 30 | prettyPrint(title, text, '#E6A23C'); 31 | }; 32 | const success = (textOrTitle: string, content = '') => { 33 | const title = isEmpty(content) ? 'Success ' : textOrTitle; 34 | const text = isEmpty(content) ? textOrTitle : content; 35 | prettyPrint(title, text, '#67C23A'); 36 | }; 37 | const table = () => { 38 | const data = [ 39 | { id: 1, name: 'Alice', age: 25 }, 40 | { id: 2, name: 'Bob', age: 30 }, 41 | { id: 3, name: 'Charlie', age: 35 }, 42 | ]; 43 | console.log( 44 | '%c id%c name%c age', 45 | 'color: white; background-color: black; padding: 2px 10px;', 46 | 'color: white; background-color: black; padding: 2px 10px;', 47 | 'color: white; background-color: black; padding: 2px 10px;', 48 | ); 49 | 50 | data.forEach((row: any) => { 51 | console.log( 52 | `%c ${row.id} %c ${row.name} %c ${row.age} `, 53 | 'color: black; background-color: lightgray; padding: 2px 10px;', 54 | 'color: black; background-color: lightgray; padding: 2px 10px;', 55 | 'color: black; background-color: lightgray; padding: 2px 10px;', 56 | ); 57 | }); 58 | }; 59 | const picture = (url: string, scale = 1) => { 60 | if (isProduction) return; 61 | const img = new Image(); 62 | img.crossOrigin = 'anonymous'; 63 | img.onload = () => { 64 | const c = document.createElement('canvas'); 65 | const ctx = c.getContext('2d'); 66 | if (ctx) { 67 | c.width = img.width; 68 | c.height = img.height; 69 | ctx.fillStyle = 'red'; 70 | ctx.fillRect(0, 0, c.width, c.height); 71 | ctx.drawImage(img, 0, 0); 72 | const dataUri = c.toDataURL('image/png'); 73 | 74 | console.log( 75 | '%c sup?', 76 | `font-size: 1px; 77 | padding: ${Math.floor((img.height * scale) / 2)}px ${Math.floor((img.width * scale) / 2)}px; 78 | background-image: url(${dataUri}); 79 | background-repeat: no-repeat; 80 | background-size: ${img.width * scale}px ${img.height * scale}px; 81 | color: transparent; 82 | `, 83 | ); 84 | } 85 | }; 86 | img.src = url; 87 | }; 88 | 89 | return { 90 | info, 91 | error, 92 | warning, 93 | success, 94 | picture, 95 | table, 96 | }; 97 | }; 98 | // 创建打印对象 99 | const log = prettyLog(); 100 | -------------------------------------------------------------------------------- /inputNumberRange/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 197 | 230 | -------------------------------------------------------------------------------- /fetchUtil/fetch.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-async-promise-executor */ 2 | import * as qs from 'qs'; 3 | 4 | const isHttpURL = /^https?:\/\//i; 5 | interface HttpConfig { 6 | // 请求URL的前缀,如果传入的url不为完整路径时会自动拼接此参数 7 | baseURL?: string; 8 | // 请求参数,最终会拼接在URL上 9 | params?: URLSearchParams | object; 10 | // params不是URLSearchParams类型时,使用此方法序列化为字符串 11 | paramsSerializer?: (params: any) => string 12 | // 请求体,属性禁止传值 13 | readonly body?: BodyInit | null; 14 | // 提交的数据,最终会被处理为对应的格式,放在body中 15 | data?: FormData | URLSearchParams | object | string, 16 | // 序列化data的方法,只有当data的类型不为FormData和URLSearchParams时,并且请求方式指定为表单提交时执行 17 | dataSerializer?: (params: any) => string 18 | // 请求头 19 | headers?: HeadersInit; 20 | // 请求方式 21 | method?: string; 22 | // 返回值类型 23 | responseType?: 'arrayBuffer' | 'blob' | 'formData' | 'json' | 'text'; 24 | } 25 | 26 | export interface ResponseResult { 27 | code: number; 28 | message?: string; 29 | data?: T 30 | } 31 | 32 | const fetchUtil = { 33 | create: (config?: HttpConfig) => { 34 | let interceptorRequest: (init: RequestInit) => RequestInit; 35 | let interceptorResponse: (result: ResponseResult) => ResponseResult | Promise; 36 | let interceptorResponseErr: (reason: string) => string; 37 | const baseConfig = config; 38 | 39 | // 最终请求时使用的函数 40 | const customFetch = (input: RequestInfo | URL, init: RequestInit, responseType?: 'arrayBuffer' | 'blob' | 'formData' | 'json' | 'text'): Promise> => { 41 | // 在这里可以对所有请求进行拦截处理 42 | const cfg = interceptorRequest(init); 43 | return new Promise(async (resolve, reject) => { 44 | // 发起fetch请求,网络错误不会出现异常,需要在下面进行处理 45 | const response: Response = await fetch(input, cfg); 46 | if (response.ok) { 47 | // 表示网络上请求成功了,也就是http请求是成功了,但是具体的业务层是否成功后续根据正文中的code判断 48 | let result: ResponseResult; 49 | if (responseType === 'arrayBuffer') { 50 | result = { 51 | code: 200, 52 | data: await response.arrayBuffer(), 53 | }; 54 | } else if (responseType === 'blob') { 55 | result = { 56 | code: 200, 57 | data: await response.blob(), 58 | }; 59 | } else if (responseType === 'formData') { 60 | result = { 61 | code: 200, 62 | data: await response.formData(), 63 | }; 64 | } else if (responseType === 'text') { 65 | result = { 66 | code: 200, 67 | data: await response.text(), 68 | }; 69 | } else { 70 | result = await response.json(); 71 | } 72 | // 此处进行响应拦截处理 73 | resolve(interceptorResponse(result)); 74 | } else { 75 | reject(interceptorResponseErr(response.statusText)); 76 | } 77 | }); 78 | }; 79 | 80 | /** 81 | * 82 | * @param url url 83 | * @param init 初始参数,优先级比全局的更高 84 | */ 85 | function request(url: string, init?: HttpConfig): Promise> { 86 | const config = { 87 | ...baseConfig, 88 | ...init, 89 | }; 90 | // 转换为Headers类型,方便操作 91 | const headers: Headers = new Headers(config?.headers); 92 | if (config.data instanceof FormData) { 93 | // 上传文件时的类型 94 | headers.set('content-type', 'form-data/multipart'); 95 | config.headers = headers; 96 | config.body = config.data; 97 | } else if (config.data instanceof URLSearchParams) { 98 | // 表单提交 99 | headers.set('content-type', 'application/x-www-form-urlencoded'); 100 | config.headers = headers; 101 | // URLSearchParams转字符串 102 | config.body = config.data.toString(); 103 | } else { 104 | const contentType = headers.get('content-type'); 105 | if (contentType === 'application/x-www-form-urlencoded') { 106 | // 表单提交 107 | if (config.dataSerializer) { 108 | config.body = config.dataSerializer(config.data); 109 | } else { 110 | config.body = qs.stringify(config.data, { allowDots: true }); 111 | } 112 | } else if (contentType === 'application/json') { 113 | config.body = JSON.stringify(config.data); 114 | } 115 | } 116 | // 获取URL和query参数 117 | const [URLAddress, params] = url.split('?'); 118 | // url上的参数 119 | const urlSearchParams = new URLSearchParams(params); 120 | let queryString = urlSearchParams.toString(); 121 | let paramStr; 122 | let replaceRequestURL; 123 | if (config.params instanceof URLSearchParams) { 124 | paramStr = config.params.toString(); 125 | } else if (config.paramsSerializer) { 126 | // 将params参数转字符串 127 | paramStr = config.paramsSerializer(config.params) || ''; 128 | } else { 129 | paramStr = qs.stringify(config.params, { allowDots: true }) || ''; 130 | } 131 | if (queryString && paramStr) { 132 | queryString = `${queryString}&${paramStr}`; 133 | } else { 134 | queryString += paramStr; 135 | } 136 | if (queryString) { 137 | replaceRequestURL = `${URLAddress}?${queryString}`; 138 | } 139 | if (isHttpURL.test(URLAddress)) { 140 | return customFetch(URLAddress, config, config.responseType); 141 | } 142 | if (config.baseURL) { 143 | replaceRequestURL = config.baseURL + URLAddress; 144 | } 145 | return customFetch(replaceRequestURL ?? URLAddress, config, config.responseType); 146 | } 147 | 148 | ['delete', 'get', 'head', 'options'].forEach((method) => { 149 | request.prototype[method] = (url: string, params: URLSearchParams | object, config: HttpConfig) => request(url, { 150 | ...config, 151 | method, 152 | params, 153 | }); 154 | }); 155 | 156 | ['post', 'put', 'patch'].forEach((method) => { 157 | request.prototype[method] = (url: string, data: FormData | URLSearchParams | object | string, config: HttpConfig) => request(url, { 158 | ...config, 159 | method, 160 | data, 161 | }); 162 | }); 163 | request.interceptors = { 164 | request: { 165 | use(callback: (init: RequestInit) => RequestInit) { 166 | interceptorRequest = callback; 167 | }, 168 | }, 169 | response: { 170 | use(callback1: (result: ResponseResult) => ResponseResult | Promise, callback2: (reason: string) => string) { 171 | interceptorResponse = callback1; 172 | interceptorResponseErr = callback2; 173 | }, 174 | }, 175 | }; 176 | return request; 177 | }, 178 | }; 179 | export default fetchUtil; 180 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | "types": ["@dcloudio/types"], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------