@gindow/vue 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/index.cjs +2 -0
- package/dist/index.mjs +406 -0
- package/dist/style.css +3 -0
- package/dist/vue-go.d.ts +394 -0
- package/package.json +99 -0
- package/src/env.d.ts +7 -0
- package/src/hooks/useBreak.ts +23 -0
- package/src/hooks/useCaptcha.ts +29 -0
- package/src/hooks/useChat.ts +135 -0
- package/src/hooks/useNanoid.ts +9 -0
- package/src/hooks/useUpload.ts +59 -0
- package/src/index.ts +29 -0
- package/src/locale/en-US.ts +11 -0
- package/src/locale/index.ts +55 -0
- package/src/locale/zh-CN.ts +11 -0
- package/src/style.css +24 -0
- package/src/types/chat.ts +62 -0
- package/src/types/index.ts +74 -0
- package/src/utils/datetime.ts +42 -0
- package/src/utils/download.ts +11 -0
- package/src/utils/formatter.ts +42 -0
- package/src/utils/get.ts +10 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/platform.ts +38 -0
- package/src/utils/request.ts +146 -0
- package/src/utils/validate.ts +22 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fileTypeChecker from 'file-type-checker'
|
|
2
|
+
import heic2any from 'heic2any'
|
|
3
|
+
import Compressor from 'compressorjs'
|
|
4
|
+
|
|
5
|
+
export const useUpload = () => {
|
|
6
|
+
|
|
7
|
+
const HEIC = (file: File): Promise<File> => {
|
|
8
|
+
return new Promise(async resolve => {
|
|
9
|
+
const blob = (await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.9 }) as Blob)
|
|
10
|
+
const newFile = new File([blob], file.name, { type: blob.type, lastModified: Date.now() })
|
|
11
|
+
return resolve(newFile)
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const compressor = (file: File): Promise<File> => {
|
|
16
|
+
return new Promise(resolve => {
|
|
17
|
+
new Compressor(file, { quality: 0.9, success: (file: File) => resolve(file) })
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const handler = (
|
|
22
|
+
file: File,
|
|
23
|
+
para: { compressor: boolean } = { compressor: true }
|
|
24
|
+
): Promise<File> => {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
const reader = new FileReader()
|
|
27
|
+
reader.readAsArrayBuffer(file)
|
|
28
|
+
reader.onload = () => {
|
|
29
|
+
const isHeic = fileTypeChecker.validateFileType(reader.result as ArrayBuffer, ['heic'])
|
|
30
|
+
const isCompressor = fileTypeChecker.validateFileType(reader.result as ArrayBuffer, ['jpeg', 'png', 'gif', 'bmp'])
|
|
31
|
+
if (isHeic) resolve(HEIC(file))
|
|
32
|
+
else if (isCompressor && para.compressor) resolve(compressor(file))
|
|
33
|
+
else resolve(file)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const getDimension = async (blob: Blob): Promise<{ width: number; height: number }> => {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const img = new Image()
|
|
41
|
+
img.src = URL.createObjectURL(blob)
|
|
42
|
+
img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight })
|
|
43
|
+
img.onerror = (err) => reject(err)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getFileType = (file: File): Promise<{ mimeType: string }> => {
|
|
48
|
+
return new Promise(resolve => {
|
|
49
|
+
const reader = new FileReader()
|
|
50
|
+
reader.readAsArrayBuffer(file)
|
|
51
|
+
reader.onload = () => {
|
|
52
|
+
const detectedFile = fileTypeChecker.detectFile(reader.result as ArrayBuffer)
|
|
53
|
+
resolve({ mimeType: detectedFile?.mimeType! })
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { handler, getDimension, getFileType }
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { App, Plugin } from 'vue'
|
|
2
|
+
import { provider, setLocale, getLocale, useLocale } from './locale'
|
|
3
|
+
import './style.css'
|
|
4
|
+
|
|
5
|
+
const VueGo: Plugin = {
|
|
6
|
+
install(app, options?: { locale?: string; messages?: Record<string, any> }) {
|
|
7
|
+
provider(options)
|
|
8
|
+
app.config.globalProperties.$setLocale = setLocale
|
|
9
|
+
app.config.globalProperties.$getLocale = getLocale
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export * from './types'
|
|
14
|
+
export * from './types/chat'
|
|
15
|
+
export * from './utils'
|
|
16
|
+
export { useBreak } from './hooks/useBreak'
|
|
17
|
+
export { useNanoid } from './hooks/useNanoid'
|
|
18
|
+
export { useCaptcha } from './hooks/useCaptcha'
|
|
19
|
+
export { useUpload } from './hooks/useUpload'
|
|
20
|
+
export { useChat } from './hooks/useChat'
|
|
21
|
+
export type { IChatMessage, IMessageEvent, IUsageEvent, IErrorEvent, ISendMessageParams } from './hooks/useChat'
|
|
22
|
+
|
|
23
|
+
export default VueGo
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
setLocale,
|
|
27
|
+
getLocale,
|
|
28
|
+
useLocale,
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { inject, provide, reactive, ref } from 'vue'
|
|
2
|
+
import zhCN from './zh-CN'
|
|
3
|
+
import enUS from './en-US'
|
|
4
|
+
|
|
5
|
+
type Messages = Record<string, any>
|
|
6
|
+
|
|
7
|
+
const getStoredLocale = (): string => {
|
|
8
|
+
const availableCodes = ['en', 'zh-CN']
|
|
9
|
+
const storageLocale = localStorage.getItem('locale')
|
|
10
|
+
const browserLocale = navigator.language
|
|
11
|
+
if (storageLocale && availableCodes.includes(storageLocale)) return storageLocale
|
|
12
|
+
else if (browserLocale && availableCodes.includes(browserLocale as string)) return browserLocale as string
|
|
13
|
+
else if (browserLocale && availableCodes.includes(browserLocale.split('-')[0] as string)) return browserLocale.split('-')[0] as string
|
|
14
|
+
else return availableCodes[0] ?? 'zh-CN'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LOCALE_INJECTION_KEY = Symbol('vue-go-locale')
|
|
18
|
+
const locale = ref<string>(getStoredLocale())
|
|
19
|
+
const messages: Messages = reactive({ 'en': enUS, 'zh-CN': zhCN })
|
|
20
|
+
|
|
21
|
+
export const provider = (opts?: { locale?: string; messages?: Messages }) => {
|
|
22
|
+
if (opts?.locale) locale.value = opts.locale
|
|
23
|
+
if (opts?.messages) {
|
|
24
|
+
for (const [key, value] of Object.entries(opts.messages)) {
|
|
25
|
+
messages[key] = value
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
provide(LOCALE_INJECTION_KEY, { locale, messages })
|
|
29
|
+
return { locale, messages }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const useLocale = () => {
|
|
33
|
+
const injected = inject<{ locale: typeof locale; messages: typeof messages } | undefined>(LOCALE_INJECTION_KEY, undefined)
|
|
34
|
+
const currentLocale = injected?.locale ?? locale
|
|
35
|
+
const currentMessages = injected?.messages ?? messages
|
|
36
|
+
|
|
37
|
+
const t = (path: string, params?: Record<string, string | number>): string => {
|
|
38
|
+
const msg = currentMessages[currentLocale.value]
|
|
39
|
+
const keys = path.replace(/\[(\d+)]/g, '.$1').split('.')
|
|
40
|
+
let result: any = msg
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
result = result?.[key]
|
|
43
|
+
if (result === undefined) return path
|
|
44
|
+
}
|
|
45
|
+
let text = (result ?? path) as string
|
|
46
|
+
if (params && typeof text === 'string') text = text.replace(/\{(\w+)\}/g, (_, k) => params[k] !== undefined ? String(params[k]) : `{${k}}`)
|
|
47
|
+
return text
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { locale: currentLocale, t }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const setLocale = (lang: string) => locale.value = lang
|
|
54
|
+
|
|
55
|
+
export const getLocale = () => locale.value
|
package/src/style.css
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* vue-go 样式占位文件。
|
|
3
|
+
*
|
|
4
|
+
* vue-go 本身不包含 UI 组件,只提供基础样式工具类:
|
|
5
|
+
* - TailwindCSS 基础配置
|
|
6
|
+
* - 通用 flex 布局工具类
|
|
7
|
+
* - 通用文本颜色工具类(含暗黑模式)
|
|
8
|
+
*
|
|
9
|
+
* 实际组件样式以 SFC <style> 块的形式写在各 .vue 文件中:
|
|
10
|
+
* - npm 包模式:构建时由 Vite 提取并聚合到 dist/style.css
|
|
11
|
+
* - submodule 源码模式:消费者 import .vue 时由 vue 插件自动注入
|
|
12
|
+
*/
|
|
13
|
+
@import "tailwindcss";
|
|
14
|
+
|
|
15
|
+
@source ".";
|
|
16
|
+
|
|
17
|
+
.flex-center { display: flex !important; align-items: center; justify-content: center; }
|
|
18
|
+
.flex-center-end { display: flex !important; align-items: center; justify-content: end; }
|
|
19
|
+
.flex-center-between { display: flex !important; align-items: center; justify-content: space-between; }
|
|
20
|
+
.flex-center-items { display: flex !important; align-items: center; }
|
|
21
|
+
|
|
22
|
+
.text-light { color: #ccc; }
|
|
23
|
+
|
|
24
|
+
.dark .text-light { color: #333; }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type MessageRole = 'user' | 'assistant' | 'system'
|
|
2
|
+
|
|
3
|
+
export type MessageContentType = 'text' | 'image' | 'code' | 'file'
|
|
4
|
+
|
|
5
|
+
export interface IChatFile {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
type: string
|
|
9
|
+
size: number
|
|
10
|
+
url?: string
|
|
11
|
+
thumbnail?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IChatMessageItem {
|
|
15
|
+
id: string
|
|
16
|
+
role: MessageRole
|
|
17
|
+
content: string
|
|
18
|
+
contentType?: MessageContentType
|
|
19
|
+
timestamp: number
|
|
20
|
+
isLoading?: boolean
|
|
21
|
+
isError?: boolean
|
|
22
|
+
files?: IChatFile[]
|
|
23
|
+
metadata?: Record<string, any>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IChatSession {
|
|
27
|
+
id: string
|
|
28
|
+
title: string
|
|
29
|
+
messages: IChatMessageItem[]
|
|
30
|
+
createdAt: number
|
|
31
|
+
updatedAt: number
|
|
32
|
+
isPinned?: boolean
|
|
33
|
+
isArchived?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ISendChatParams {
|
|
37
|
+
content: string
|
|
38
|
+
contentType?: MessageContentType
|
|
39
|
+
files?: IChatFile[]
|
|
40
|
+
sessionId?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IAIResponse {
|
|
44
|
+
message: IChatMessageItem
|
|
45
|
+
sessionId: string
|
|
46
|
+
suggestions?: string[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IChatConfig {
|
|
50
|
+
placeholder?: string
|
|
51
|
+
maxFileSize?: number
|
|
52
|
+
allowedFileTypes?: string[]
|
|
53
|
+
enableStreaming?: boolean
|
|
54
|
+
showTimestamp?: boolean
|
|
55
|
+
showSuggestions?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ICodeBlock {
|
|
59
|
+
language: string
|
|
60
|
+
code: string
|
|
61
|
+
filename?: string
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface IModel {
|
|
2
|
+
id?: string | any
|
|
3
|
+
created_at?: string
|
|
4
|
+
updated_at?: string
|
|
5
|
+
deleted_at?: string
|
|
6
|
+
[property: string]: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IAsset extends IModel {
|
|
10
|
+
id: string
|
|
11
|
+
type: string
|
|
12
|
+
title: string
|
|
13
|
+
url: string
|
|
14
|
+
shrink: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IField extends IModel {
|
|
18
|
+
name: string
|
|
19
|
+
type: string
|
|
20
|
+
readonly?: boolean
|
|
21
|
+
multiple?: boolean
|
|
22
|
+
required?: boolean
|
|
23
|
+
options?: { label: string; value: string }[] | any[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type IFilter = Omit<IField, 'name'> & { label?: string, rules?: any[] }
|
|
27
|
+
|
|
28
|
+
export interface IParams extends IModel {
|
|
29
|
+
filter?: Object
|
|
30
|
+
search?: string
|
|
31
|
+
include?: string
|
|
32
|
+
page?: number
|
|
33
|
+
size?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface IPagination {
|
|
37
|
+
current_page: number
|
|
38
|
+
per_page: number
|
|
39
|
+
count: number
|
|
40
|
+
total: number
|
|
41
|
+
total_pages: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface IResult {
|
|
45
|
+
code: number
|
|
46
|
+
message: string
|
|
47
|
+
data?: IModel | IModel[] | null
|
|
48
|
+
meta?: { pagination?: IPagination }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface IUploadUserFile {
|
|
52
|
+
id?: string
|
|
53
|
+
percentage?: number
|
|
54
|
+
asset?: IAsset
|
|
55
|
+
title?: string
|
|
56
|
+
width?: number
|
|
57
|
+
height?: number
|
|
58
|
+
type?: string
|
|
59
|
+
mimeType?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface IMenu {
|
|
63
|
+
key?: string
|
|
64
|
+
title: string
|
|
65
|
+
path: string
|
|
66
|
+
name?: string
|
|
67
|
+
icon?: string
|
|
68
|
+
depend?: string
|
|
69
|
+
hidden?: boolean
|
|
70
|
+
divider?: boolean
|
|
71
|
+
disabled?: boolean
|
|
72
|
+
children?: IMenu[]
|
|
73
|
+
isGroup?: boolean
|
|
74
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
|
|
3
|
+
export class DateTime {
|
|
4
|
+
|
|
5
|
+
static dayjs = dayjs
|
|
6
|
+
|
|
7
|
+
static s(date?: dayjs.ConfigType, symbol = '/') {
|
|
8
|
+
return DateTime.dayjs(date).format('YYYY/MM/DD HH:mm:ss'.replace(/\//g, symbol))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static m(date?: dayjs.ConfigType, symbol = '/') {
|
|
12
|
+
return DateTime.dayjs(date).format('YYYY/MM/DD HH:mm'.replace(/\//g, symbol))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static h(date?: dayjs.ConfigType, symbol = '/') {
|
|
16
|
+
return DateTime.dayjs(date).format('YYYY/MM/DD HH'.replace(/\//g, symbol))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static d(date?: dayjs.ConfigType, symbol = '/') {
|
|
20
|
+
return DateTime.dayjs(date).format('YYYY/MM/DD'.replace(/\//g, symbol))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static M(date?: dayjs.ConfigType, symbol = '/') {
|
|
24
|
+
return DateTime.dayjs(date).format('YYYY/MM'.replace(/\//g, symbol))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static y(date?: dayjs.ConfigType) {
|
|
28
|
+
return DateTime.dayjs(date).format('YYYY')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static date(date?: dayjs.ConfigType, symbol = '/') {
|
|
32
|
+
return DateTime.dayjs(date).format('YYYY/MM/DD'.replace(/\//g, symbol))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static time(date?: dayjs.ConfigType) {
|
|
36
|
+
return DateTime.dayjs(date).format('HH:mm')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static format(date?: dayjs.ConfigType, format = 'YYYY/MM/DD HH:mm:ss') {
|
|
40
|
+
return DateTime.dayjs(date).format(format)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const download = (blob: Blob, filename: string) => {
|
|
2
|
+
const url = URL.createObjectURL(blob)
|
|
3
|
+
const a = document.createElement('a')
|
|
4
|
+
a.href = url
|
|
5
|
+
a.download = filename
|
|
6
|
+
a.style.display = 'none'
|
|
7
|
+
document.body.appendChild(a)
|
|
8
|
+
a.click()
|
|
9
|
+
document.body.removeChild(a)
|
|
10
|
+
URL.revokeObjectURL(url)
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class Formatter {
|
|
2
|
+
|
|
3
|
+
static config: {
|
|
4
|
+
locale?: string
|
|
5
|
+
currency?: string
|
|
6
|
+
decimals?: number
|
|
7
|
+
} = {}
|
|
8
|
+
|
|
9
|
+
static set = (config: { locale?: string, currency?: string, decimals?: number }) => this.config = config
|
|
10
|
+
|
|
11
|
+
static id = (str: string = '') => str.slice(-12).toUpperCase()
|
|
12
|
+
|
|
13
|
+
static date = (datestr: string = '') => datestr.substring(0, 10) + ' ' + datestr.substring(11, 19)
|
|
14
|
+
|
|
15
|
+
static idcard = (idcard: string = '') => idcard ? idcard.substring(0, 6) + '****' + idcard.substring(14) : ''
|
|
16
|
+
|
|
17
|
+
static phone = (phone: string = '', naked = false): string => naked || !phone ? phone : phone.substring(0, 3) + '****' + phone.substring(phone.length - 4)
|
|
18
|
+
|
|
19
|
+
static email = (email: string = '') => {
|
|
20
|
+
const [username, domain] = email.split('@')
|
|
21
|
+
if (!username || !domain) return email
|
|
22
|
+
const masked = username.length > 2 ? username[0] + '****' + username[username.length - 1] : username
|
|
23
|
+
return `${masked}@${domain}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static bankcard = (bankcard: string = '') => bankcard.substring(0, 4) + '****' + bankcard.substring(bankcard.length - 4)
|
|
27
|
+
|
|
28
|
+
static url = (url: string = '') => url.replace(/:\/\//g, ':##').replace(/\/+/g, '/').replace(/:##/g, '://')
|
|
29
|
+
|
|
30
|
+
static price = (value: string | number = 0, currency?: string) => {
|
|
31
|
+
const { locale, decimals, currency: defaultCurrency } = this.config
|
|
32
|
+
const number = typeof value === 'string' ? parseFloat(value) : value
|
|
33
|
+
return new Intl.NumberFormat(locale, {
|
|
34
|
+
style: 'currency',
|
|
35
|
+
currency: currency ?? defaultCurrency ?? 'CNY',
|
|
36
|
+
minimumFractionDigits: decimals ?? 2,
|
|
37
|
+
maximumFractionDigits: decimals ?? 2,
|
|
38
|
+
}).format(number / 100)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static currency = (value: string | number = 0, currency?: string) => this.price(value, currency)
|
|
42
|
+
}
|
package/src/utils/get.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const get = (object: any, path: string | string[], defaultValue?: any): any => {
|
|
2
|
+
if (object == null) return defaultValue
|
|
3
|
+
const keys = Array.isArray(path) ? path : path.replace(/\[(\d+)]/g, '.$1').split('.').filter(Boolean)
|
|
4
|
+
let result: any = object
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
result = result?.[key]
|
|
7
|
+
if (result === undefined) return defaultValue
|
|
8
|
+
}
|
|
9
|
+
return result
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { request } from './request'
|
|
2
|
+
export { resource } from './request'
|
|
3
|
+
export { $params } from './request'
|
|
4
|
+
export { get } from './get'
|
|
5
|
+
export { download } from './download'
|
|
6
|
+
export { Formatter } from './formatter'
|
|
7
|
+
export { Validate } from './validate'
|
|
8
|
+
export { DateTime } from './datetime'
|
|
9
|
+
export { Platform } from './platform'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class Platform {
|
|
2
|
+
|
|
3
|
+
static get userAgent() {
|
|
4
|
+
return window.navigator.userAgent.toLowerCase()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
static get isFlutter() {
|
|
8
|
+
return Platform.userAgent.match(/flutter/i) !== null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static get isFlutterIOS() {
|
|
12
|
+
return Platform.isFlutter && Platform.isIOS
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static get isWeb() {
|
|
16
|
+
return window.matchMedia('(min-width: 992px)').matches
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static get isMobile() {
|
|
20
|
+
return Platform.userAgent.match(/(phone|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i) !== null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static get isIOS() {
|
|
24
|
+
return Platform.userAgent.match(/iphone|ipad|ipod|ios/i) !== null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static get isWechat() {
|
|
28
|
+
return Platform.userAgent.match(/MicroMessenger/i) !== null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static get isWxwork() {
|
|
32
|
+
return Platform.userAgent.match(/MicroMessenger/i) !== null && Platform.userAgent.match(/wxwork/i) !== null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static get isMiniprogram() {
|
|
36
|
+
return Platform.userAgent.match(/MicroMessenger/i) !== null && Platform.userAgent.match(/miniprogram/i) !== null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { AxiosInstance, AxiosResponse } from 'axios'
|
|
2
|
+
import type { IModel } from '../types'
|
|
3
|
+
import { Platform } from './platform'
|
|
4
|
+
import cookie from 'js-cookie'
|
|
5
|
+
import axios from 'axios'
|
|
6
|
+
|
|
7
|
+
const $params = (params: IModel) => {
|
|
8
|
+
const para = { ...params }
|
|
9
|
+
if (typeof para?.search === 'string') return para
|
|
10
|
+
if (para?.search)
|
|
11
|
+
para.search = Object.keys(para.search)
|
|
12
|
+
.filter(item => para.search[item] !== null && para.search[item] !== '')
|
|
13
|
+
.map(item => item + ':' + para.search[item])
|
|
14
|
+
.join(';')
|
|
15
|
+
if (para?.searchFields)
|
|
16
|
+
para.searchFields = Object.keys(para.searchFields)
|
|
17
|
+
.filter(item => para.searchFields[item])
|
|
18
|
+
.map(item => item + ':' + para.searchFields[item])
|
|
19
|
+
.join(';')
|
|
20
|
+
return para
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const request = {
|
|
24
|
+
|
|
25
|
+
baseURL: '',
|
|
26
|
+
publicKey: '',
|
|
27
|
+
headers: { Accept: 'application/json' } as Record<string, string>,
|
|
28
|
+
instance: null as AxiosInstance|null,
|
|
29
|
+
accessToken: 'accessToken',
|
|
30
|
+
|
|
31
|
+
init (config: { baseURL?: string, publicKey?: string } = {}) {
|
|
32
|
+
this.baseURL = config.baseURL ?? ''
|
|
33
|
+
this.publicKey = config.publicKey ?? ''
|
|
34
|
+
|
|
35
|
+
const reload = () => {
|
|
36
|
+
this.delToken()
|
|
37
|
+
location.reload()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.instance = axios.create({ baseURL: this.baseURL })
|
|
41
|
+
|
|
42
|
+
this.instance.interceptors.request.use(config => {
|
|
43
|
+
const language = localStorage.getItem('locale')
|
|
44
|
+
if (language) config.headers['Accept-Language'] = language
|
|
45
|
+
return config
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.instance.interceptors.response.use((response: AxiosResponse) => {
|
|
49
|
+
if (response.config.responseType === 'blob') return response
|
|
50
|
+
else if (response.data.code === 401) return reload()
|
|
51
|
+
else if (response.data.code === 200) return response.data
|
|
52
|
+
else return Promise.reject(response.data)
|
|
53
|
+
}, ({ response }) => Promise.reject(response.data))
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
setToken (token: string, options: { expires?: number, domain?: string } = {}) {
|
|
57
|
+
options = { ...options, domain: '.' + location.hostname }
|
|
58
|
+
cookie.set(this.accessToken, token, options)
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
getToken () {
|
|
62
|
+
return cookie.get(this.accessToken) || ''
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
delToken () {
|
|
66
|
+
cookie.remove(this.accessToken, { domain: '.' + location.hostname })
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
get (url: string, params?: {}, auth = false) { return this.request({ method: 'GET', url, params }, auth) },
|
|
70
|
+
put (url: string, data?: {}, auth = false) { return this.request({ method: 'PUT', url, data }, auth) },
|
|
71
|
+
patch (url: string, data?: {}, auth = false) { return this.request({ method: 'PATCH', url, data }, auth) },
|
|
72
|
+
delete (url: string, params?: {}, auth = false) { return this.request({ method: 'DELETE', url, params }, auth) },
|
|
73
|
+
post (url: string, data?: {}, auth = false) { return this.request({ method: 'POST', url, data }, auth) },
|
|
74
|
+
blob (url: string, params?: {}, auth = false) { return this.request({ method: 'GET', url, params, responseType: 'blob' }, auth) },
|
|
75
|
+
|
|
76
|
+
request(para: any, auth: boolean = false) {
|
|
77
|
+
const headers = para.headers ?? this.headers
|
|
78
|
+
if (auth) headers.Authorization = 'Bearer ' + this.getToken()
|
|
79
|
+
if (para.params) para.params = $params(para.params)
|
|
80
|
+
return (this.instance as any)({ ...para, headers })
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async download(url: string, params?: {}, filename?: string, auth = false) {
|
|
84
|
+
const ret = await this.blob(url, params, auth)
|
|
85
|
+
if (Platform.isFlutter) return ret
|
|
86
|
+
const getResponseFileName = () => {
|
|
87
|
+
const disposition = ret.headers['content-disposition']
|
|
88
|
+
const parts = disposition.split("filename*=")
|
|
89
|
+
const val = parts[1].split(";")[0].trim()
|
|
90
|
+
return decodeURIComponent(val.replace(/^UTF-8''/i, ''))
|
|
91
|
+
}
|
|
92
|
+
let link = document.createElement('a')
|
|
93
|
+
link.href = window.URL.createObjectURL(ret.data)
|
|
94
|
+
link.download = filename ?? getResponseFileName()
|
|
95
|
+
link.click()
|
|
96
|
+
return ret
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async upload(url: string, data = {}) {
|
|
100
|
+
const headers = { ...this.headers, 'Content-Type': 'multipart/form-data' }
|
|
101
|
+
return this.instance!.post(url, data, { headers })
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
sse (url: string, para?: {[key: string]: string | number | undefined | null | void} | null) {
|
|
105
|
+
const params = para ? Object.keys(para).map(key => key +'='+ para[key]).join('&') : ''
|
|
106
|
+
return new EventSource(this.baseURL + '/' + url + '?' + params)
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
setHeader(key: string, value: string) {
|
|
110
|
+
this.headers[key] = value
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
delHeader(key: string) {
|
|
114
|
+
delete this.headers[key]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class resource {
|
|
119
|
+
|
|
120
|
+
url: string
|
|
121
|
+
auth: boolean
|
|
122
|
+
|
|
123
|
+
constructor(url: string, auth = false) {
|
|
124
|
+
this.url = url
|
|
125
|
+
this.auth = auth
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
select = (para?: {}) => request.get(this.url, para, this.auth)
|
|
129
|
+
|
|
130
|
+
create = (para?: {}) => request.post(this.url, para, this.auth)
|
|
131
|
+
|
|
132
|
+
find = (id: string, para?: {}) => request.get(this.url + '/' + id, para, this.auth)
|
|
133
|
+
|
|
134
|
+
update = (id: string, para?: {}) => request.patch(this.url + '/' + id, para, this.auth)
|
|
135
|
+
|
|
136
|
+
delete = (id: string, para?: {}) => request.delete(this.url + '/' + id, para, this.auth)
|
|
137
|
+
|
|
138
|
+
async get(para: any) {
|
|
139
|
+
para.size = 1
|
|
140
|
+
const ret = await this.select(para)
|
|
141
|
+
ret.data = ret.data[0] || {}
|
|
142
|
+
return ret
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { $params }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class Validate {
|
|
2
|
+
|
|
3
|
+
static country: string = ''
|
|
4
|
+
|
|
5
|
+
static set = (country: string) => this.country = country
|
|
6
|
+
|
|
7
|
+
static get phone_pattern () {
|
|
8
|
+
return this.country.toUpperCase() === 'CN'
|
|
9
|
+
? /([1][3,4,5,6,7,8,9][0-9]{9})$/
|
|
10
|
+
: /^(?:\+?[1-9]\d{7,14}|\d{6,11})$/
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static email_pattern = /^([a-zA-Z0-9_\.-]+)@([\da-zA-Z\.-]+)\.([a-zA-Z\.]{2,6})$/
|
|
14
|
+
static idcard_pattern = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
|
|
15
|
+
static cname_pattern = /^(?:[\u4e00-\u9fa5·]{2,16})$/
|
|
16
|
+
static password_pattern = /^(?![A-Za-z]+$)(?![0-9]+$)(?![^A-Za-z0-9]+$).{8,20}$/
|
|
17
|
+
|
|
18
|
+
static phone = (str: string) => Validate.phone_pattern.test(str)
|
|
19
|
+
static email = (str: string) => Validate.email_pattern.test(str)
|
|
20
|
+
static idcard = (str: string) => Validate.idcard_pattern.test(str)
|
|
21
|
+
static cname = (str: string) => Validate.cname_pattern.test(str)
|
|
22
|
+
}
|