vome-core 0.0.44 → 0.0.46
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/dist/admin/config/plugin-dev.js +54 -1
- package/dist/admin/crud/components/vm-preview-viewer.vue +19 -10
- package/dist/admin/crud/components/vm-upload-item.vue +31 -14
- package/dist/admin/crud/components/vm-upload.vue +3 -0
- package/dist/admin/crud/config.js +33 -1
- package/dist/admin/crud/confirm.js +97 -1
- package/dist/admin/crud/dict.js +48 -1
- package/dist/admin/crud/index.js +74 -1
- package/dist/admin/crud/key.js +20 -1
- package/dist/admin/crud/mitt.js +21 -1
- package/dist/admin/crud/plugins.js +216 -1
- package/dist/admin/crud/span.js +35 -1
- package/dist/admin/crud/style.js +74 -1
- package/dist/admin/crud/validate.js +92 -1
- package/dist/admin/directives/perm.js +14 -1
- package/dist/admin/hooks/useUpload.js +63 -1
- package/dist/admin/lib/browser.js +24 -1
- package/dist/admin/lib/cn.js +5 -1
- package/dist/admin/lib/dialog-float.js +13 -1
- package/dist/admin/lib/export-excel.js +64 -1
- package/dist/admin/lib/file-preview.js +74 -1
- package/dist/admin/lib/import-excel.js +61 -1
- package/dist/admin/lib/json.js +42 -1
- package/dist/admin/lib/menu.js +11 -1
- package/dist/admin/lib/tree.js +1 -1
- package/dist/admin/lib/upload.js +88 -1
- package/dist/admin/lib/video-frame.js +80 -1
- package/dist/index.js +28021 -1
- package/dist/server/index.js +31407 -1
- package/dist/shared/excel.js +93 -1
- package/dist/shared/index.js +14 -1
- package/dist/shared/tree.js +39 -1
- package/package.json +1 -1
|
@@ -1 +1,216 @@
|
|
|
1
|
-
|
|
1
|
+
import { findEpsEntity } from "../lib/eps";
|
|
2
|
+
import { getCrudStyle } from "./style";
|
|
3
|
+
export function toTree(options = {}) {
|
|
4
|
+
return {
|
|
5
|
+
__plugin: "toTree",
|
|
6
|
+
tree: true,
|
|
7
|
+
lazy: options.lazy ?? false
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function setFocus(prop) {
|
|
11
|
+
return {
|
|
12
|
+
__plugin: "setFocus",
|
|
13
|
+
prop: prop ?? ""
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function setRules() {
|
|
17
|
+
return {
|
|
18
|
+
__plugin: "setRules"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function setAuto(options = { hideLabel: true }) {
|
|
22
|
+
return {
|
|
23
|
+
__plugin: "setAuto",
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const Plugins = {
|
|
28
|
+
Table: { toTree },
|
|
29
|
+
Form: { setFocus, setRules },
|
|
30
|
+
Search: { setAuto }
|
|
31
|
+
};
|
|
32
|
+
export function applyTablePlugins(options) {
|
|
33
|
+
const style = getCrudStyle().table;
|
|
34
|
+
const base = {
|
|
35
|
+
border: style.border,
|
|
36
|
+
autoHeight: style.autoHeight,
|
|
37
|
+
contextMenu: style.contextMenu,
|
|
38
|
+
...options
|
|
39
|
+
};
|
|
40
|
+
const plugins = [
|
|
41
|
+
...style.plugins,
|
|
42
|
+
...options?.plugins || []
|
|
43
|
+
];
|
|
44
|
+
const next = { ...base };
|
|
45
|
+
delete next.plugins;
|
|
46
|
+
for (const p of plugins) {
|
|
47
|
+
if (!p || typeof p !== "object")
|
|
48
|
+
continue;
|
|
49
|
+
if (p.__plugin === "toTree") {
|
|
50
|
+
next.tree = true;
|
|
51
|
+
next.treeLazy = Boolean(p.lazy);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
function stripAlias(name) {
|
|
57
|
+
return name.includes(".") ? name.split(".").pop() : name;
|
|
58
|
+
}
|
|
59
|
+
function parseRef(ref) {
|
|
60
|
+
if (typeof ref === "string") {
|
|
61
|
+
const column = stripAlias(ref);
|
|
62
|
+
return { column, param: column, multiple: true, none: false };
|
|
63
|
+
}
|
|
64
|
+
if (!ref || typeof ref !== "object")
|
|
65
|
+
return null;
|
|
66
|
+
const o = ref;
|
|
67
|
+
if (!o.column)
|
|
68
|
+
return null;
|
|
69
|
+
const column = stripAlias(o.column);
|
|
70
|
+
return {
|
|
71
|
+
column,
|
|
72
|
+
param: o.requestParam || column,
|
|
73
|
+
label: o.label,
|
|
74
|
+
dict: o.dict,
|
|
75
|
+
multiple: o.multiple !== false,
|
|
76
|
+
none: Boolean(o.none)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function displayName(ref, meta) {
|
|
80
|
+
return ref.label || meta?.comment || ref.column;
|
|
81
|
+
}
|
|
82
|
+
export function buildAutoSearchItems(service, options = {}) {
|
|
83
|
+
const ignore = new Set(options.ignoreFields || []);
|
|
84
|
+
const search = service.search || {};
|
|
85
|
+
let fieldEq = search.fieldEq || [];
|
|
86
|
+
let fieldLike = search.fieldLike || [];
|
|
87
|
+
let fieldArray = search.fieldArray || [];
|
|
88
|
+
let fieldRange = search.fieldRange || [];
|
|
89
|
+
let keyWordLikeFields = search.keyWordLikeFields || [];
|
|
90
|
+
let cols = [];
|
|
91
|
+
const entity = service.namespace ? findEpsEntity(`/${service.namespace}`) : undefined;
|
|
92
|
+
if (entity) {
|
|
93
|
+
cols = [
|
|
94
|
+
...entity.columns || [],
|
|
95
|
+
...entity.pageColumns || []
|
|
96
|
+
];
|
|
97
|
+
if (!service.search) {
|
|
98
|
+
const op = entity.pageQueryOp;
|
|
99
|
+
if (op) {
|
|
100
|
+
fieldEq = op.fieldEq || [];
|
|
101
|
+
fieldLike = op.fieldLike || [];
|
|
102
|
+
fieldArray = op.fieldArray || [];
|
|
103
|
+
fieldRange = op.fieldRange || [];
|
|
104
|
+
keyWordLikeFields = op.keyWordLikeFields || [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const colOf = (name) => cols.find((c) => c.propertyName === name);
|
|
109
|
+
const items = [];
|
|
110
|
+
const usedProps = new Set;
|
|
111
|
+
const pushItem = (item) => {
|
|
112
|
+
if (!item.prop || usedProps.has(item.prop))
|
|
113
|
+
return;
|
|
114
|
+
usedProps.add(item.prop);
|
|
115
|
+
items.push(item);
|
|
116
|
+
};
|
|
117
|
+
for (const raw of fieldLike) {
|
|
118
|
+
const ref = parseRef(raw);
|
|
119
|
+
if (!ref || ref.none || ignore.has(ref.param))
|
|
120
|
+
continue;
|
|
121
|
+
const meta = colOf(ref.column);
|
|
122
|
+
const name = displayName(ref, meta);
|
|
123
|
+
pushItem({
|
|
124
|
+
prop: ref.param,
|
|
125
|
+
label: options.hideLabel ? "" : name,
|
|
126
|
+
placeholder: `搜索${name}`,
|
|
127
|
+
type: "input"
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
for (const raw of fieldArray) {
|
|
131
|
+
const ref = parseRef(raw);
|
|
132
|
+
if (!ref || ref.none || ignore.has(ref.param))
|
|
133
|
+
continue;
|
|
134
|
+
const meta = colOf(ref.column);
|
|
135
|
+
const name = displayName(ref, meta);
|
|
136
|
+
pushItem({
|
|
137
|
+
prop: ref.param,
|
|
138
|
+
label: options.hideLabel ? "" : name,
|
|
139
|
+
placeholder: `搜索${name}`,
|
|
140
|
+
type: "input"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
for (const raw of fieldEq) {
|
|
144
|
+
const ref = parseRef(raw);
|
|
145
|
+
if (!ref || ref.none || ignore.has(ref.param))
|
|
146
|
+
continue;
|
|
147
|
+
const meta = colOf(ref.column);
|
|
148
|
+
const dictKey = ref.dict || (typeof meta?.dict === "string" ? meta.dict : Array.isArray(meta?.dict) ? meta.dict[0] : undefined);
|
|
149
|
+
const name = displayName(ref, meta);
|
|
150
|
+
const label = options.hideLabel ? "" : name;
|
|
151
|
+
if (dictKey) {
|
|
152
|
+
pushItem({
|
|
153
|
+
prop: ref.param,
|
|
154
|
+
label,
|
|
155
|
+
placeholder: `选择${name}`,
|
|
156
|
+
type: "select",
|
|
157
|
+
dict: dictKey,
|
|
158
|
+
multiple: ref.multiple
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
pushItem({
|
|
162
|
+
prop: ref.param,
|
|
163
|
+
label,
|
|
164
|
+
placeholder: `搜索${name}`,
|
|
165
|
+
type: "input"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const r of fieldRange) {
|
|
170
|
+
if (!r?.column || r.none)
|
|
171
|
+
continue;
|
|
172
|
+
if (ignore.has(r.min) || ignore.has(r.max))
|
|
173
|
+
continue;
|
|
174
|
+
const col = stripAlias(r.column);
|
|
175
|
+
const meta = colOf(col);
|
|
176
|
+
const name = r.label || meta?.comment || col;
|
|
177
|
+
const label = options.hideLabel ? "" : name;
|
|
178
|
+
const isNum = r.type === "int" || r.type === "float";
|
|
179
|
+
const startHint = isNum ? "最小值" : "开始";
|
|
180
|
+
const endHint = isNum ? "最大值" : "结束";
|
|
181
|
+
pushItem({
|
|
182
|
+
prop: `__range_${r.min}_${r.max}`,
|
|
183
|
+
label,
|
|
184
|
+
placeholder: name || "区间",
|
|
185
|
+
type: isNum ? "number-range" : "daterange",
|
|
186
|
+
range: { min: r.min, max: r.max, rangeType: r.type },
|
|
187
|
+
component: {
|
|
188
|
+
name: isNum ? "vm-number-range" : "vm-date-range",
|
|
189
|
+
props: isNum ? {
|
|
190
|
+
mode: r.type,
|
|
191
|
+
startPlaceholder: `${name}${startHint}`,
|
|
192
|
+
endPlaceholder: `${name}${endHint}`
|
|
193
|
+
} : {
|
|
194
|
+
precision: r.type,
|
|
195
|
+
startPlaceholder: `${name}${startHint}`,
|
|
196
|
+
endPlaceholder: `${name}${endHint}`
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (keyWordLikeFields.length) {
|
|
202
|
+
const names = keyWordLikeFields.map((raw) => {
|
|
203
|
+
const ref = parseRef(raw);
|
|
204
|
+
if (!ref)
|
|
205
|
+
return typeof raw === "string" ? stripAlias(raw) : "";
|
|
206
|
+
return displayName(ref, colOf(ref.column));
|
|
207
|
+
}).filter(Boolean);
|
|
208
|
+
pushItem({
|
|
209
|
+
prop: "keyWord",
|
|
210
|
+
label: options.hideLabel ? "" : "关键字",
|
|
211
|
+
placeholder: `搜索${names.join("、") || "关键字"}`,
|
|
212
|
+
type: "input"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return items;
|
|
216
|
+
}
|
package/dist/admin/crud/span.js
CHANGED
|
@@ -1 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
import { onMounted, onUnmounted, ref } from "vue";
|
|
2
|
+
import { getCrudStyle } from "./style";
|
|
3
|
+
export const FORM_COLS_DESKTOP = 24;
|
|
4
|
+
export const FORM_COLS_MOBILE = 12;
|
|
5
|
+
export const FORM_SPAN_FULL = 24;
|
|
6
|
+
export const FORM_MQ_MOBILE = "(max-width: 768px)";
|
|
7
|
+
export function getFormCols() {
|
|
8
|
+
if (typeof window !== "undefined" && window.matchMedia(FORM_MQ_MOBILE).matches) {
|
|
9
|
+
return FORM_COLS_MOBILE;
|
|
10
|
+
}
|
|
11
|
+
return FORM_COLS_DESKTOP;
|
|
12
|
+
}
|
|
13
|
+
export function resolveFormSpan(span, cols = getFormCols()) {
|
|
14
|
+
const raw = span ?? getCrudStyle().form.span ?? FORM_SPAN_FULL;
|
|
15
|
+
const normalized = Math.min(FORM_SPAN_FULL, Math.max(1, raw));
|
|
16
|
+
if (cols === FORM_COLS_DESKTOP)
|
|
17
|
+
return normalized;
|
|
18
|
+
return Math.max(1, Math.round(normalized / FORM_COLS_DESKTOP * FORM_COLS_MOBILE));
|
|
19
|
+
}
|
|
20
|
+
export function useFormCols() {
|
|
21
|
+
const cols = ref(getFormCols());
|
|
22
|
+
let mql = null;
|
|
23
|
+
const sync = () => {
|
|
24
|
+
cols.value = getFormCols();
|
|
25
|
+
};
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
mql = window.matchMedia(FORM_MQ_MOBILE);
|
|
28
|
+
mql.addEventListener("change", sync);
|
|
29
|
+
sync();
|
|
30
|
+
});
|
|
31
|
+
onUnmounted(() => {
|
|
32
|
+
mql?.removeEventListener("change", sync);
|
|
33
|
+
});
|
|
34
|
+
return cols;
|
|
35
|
+
}
|
package/dist/admin/crud/style.js
CHANGED
|
@@ -1 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
import { CRUD_LABELS, DEFAULT_CRUD_DICT } from "./dict";
|
|
2
|
+
export const DEFAULT_CRUD_STYLE = {
|
|
3
|
+
form: {
|
|
4
|
+
labelPosition: "top",
|
|
5
|
+
labelWidth: "100px",
|
|
6
|
+
span: 24,
|
|
7
|
+
plugins: []
|
|
8
|
+
},
|
|
9
|
+
table: {
|
|
10
|
+
border: false,
|
|
11
|
+
highlightCurrentRow: true,
|
|
12
|
+
autoHeight: true,
|
|
13
|
+
contextMenu: ["refresh", "check", "edit", "delete", "order-asc", "order-desc"],
|
|
14
|
+
column: {
|
|
15
|
+
align: "left",
|
|
16
|
+
opWidth: 180
|
|
17
|
+
},
|
|
18
|
+
plugins: []
|
|
19
|
+
},
|
|
20
|
+
search: {
|
|
21
|
+
plugins: []
|
|
22
|
+
},
|
|
23
|
+
colors: [
|
|
24
|
+
"#4E5DFF",
|
|
25
|
+
"#06b31c",
|
|
26
|
+
"#e93f4d",
|
|
27
|
+
"#d57121",
|
|
28
|
+
"#6d17c3",
|
|
29
|
+
"#04c273",
|
|
30
|
+
"#aa7a24",
|
|
31
|
+
"#1c109d"
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
let globalConfig = {
|
|
35
|
+
dict: { ...DEFAULT_CRUD_DICT, label: { ...CRUD_LABELS } },
|
|
36
|
+
style: structuredClone(DEFAULT_CRUD_STYLE)
|
|
37
|
+
};
|
|
38
|
+
export function setCrudConfig(partial) {
|
|
39
|
+
if (!partial)
|
|
40
|
+
return globalConfig;
|
|
41
|
+
globalConfig = {
|
|
42
|
+
dict: {
|
|
43
|
+
...globalConfig.dict,
|
|
44
|
+
...partial.dict,
|
|
45
|
+
api: { ...globalConfig.dict.api, ...partial.dict?.api },
|
|
46
|
+
pagination: { ...globalConfig.dict.pagination, ...partial.dict?.pagination },
|
|
47
|
+
search: { ...globalConfig.dict.search, ...partial.dict?.search },
|
|
48
|
+
sort: { ...globalConfig.dict.sort, ...partial.dict?.sort },
|
|
49
|
+
label: { ...globalConfig.dict.label, ...partial.dict?.label }
|
|
50
|
+
},
|
|
51
|
+
style: {
|
|
52
|
+
...globalConfig.style,
|
|
53
|
+
...partial.style,
|
|
54
|
+
form: { ...globalConfig.style.form, ...partial.style?.form },
|
|
55
|
+
table: {
|
|
56
|
+
...globalConfig.style.table,
|
|
57
|
+
...partial.style?.table,
|
|
58
|
+
column: {
|
|
59
|
+
...globalConfig.style.table.column,
|
|
60
|
+
...partial.style?.table?.column
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
search: { ...globalConfig.style.search, ...partial.style?.search },
|
|
64
|
+
colors: partial.style?.colors || globalConfig.style.colors
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return globalConfig;
|
|
68
|
+
}
|
|
69
|
+
export function getCrudConfig() {
|
|
70
|
+
return globalConfig;
|
|
71
|
+
}
|
|
72
|
+
export function getCrudStyle() {
|
|
73
|
+
return globalConfig.style;
|
|
74
|
+
}
|
|
@@ -1 +1,92 @@
|
|
|
1
|
-
|
|
1
|
+
function isEmpty(v) {
|
|
2
|
+
if (v == null || v === "")
|
|
3
|
+
return true;
|
|
4
|
+
if (typeof v === "string" && v.trim() === "")
|
|
5
|
+
return true;
|
|
6
|
+
if (Array.isArray(v) && v.length === 0)
|
|
7
|
+
return true;
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
function normalizeRules(item) {
|
|
11
|
+
const rules = [];
|
|
12
|
+
if (item.required) {
|
|
13
|
+
rules.push({
|
|
14
|
+
required: true,
|
|
15
|
+
message: `${item.label}不能为空`
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const raw = item.rules;
|
|
19
|
+
if (!raw)
|
|
20
|
+
return rules;
|
|
21
|
+
if (Array.isArray(raw)) {
|
|
22
|
+
for (const r of raw) {
|
|
23
|
+
if (r && typeof r === "object")
|
|
24
|
+
rules.push(r);
|
|
25
|
+
}
|
|
26
|
+
} else if (typeof raw === "object") {
|
|
27
|
+
rules.push(raw);
|
|
28
|
+
}
|
|
29
|
+
return rules;
|
|
30
|
+
}
|
|
31
|
+
function isItemHidden(item, form) {
|
|
32
|
+
if (typeof item.hidden === "function")
|
|
33
|
+
return item.hidden(form);
|
|
34
|
+
return Boolean(item.hidden);
|
|
35
|
+
}
|
|
36
|
+
async function validateItem(item, form) {
|
|
37
|
+
const value = form[item.prop];
|
|
38
|
+
for (const rule of normalizeRules(item)) {
|
|
39
|
+
if (rule.required && isEmpty(value)) {
|
|
40
|
+
return rule.message || `${item.label}不能为空`;
|
|
41
|
+
}
|
|
42
|
+
if (isEmpty(value))
|
|
43
|
+
continue;
|
|
44
|
+
const str = String(value);
|
|
45
|
+
if (rule.min != null && str.length < rule.min) {
|
|
46
|
+
return rule.message || `${item.label}至少 ${rule.min} 个字符`;
|
|
47
|
+
}
|
|
48
|
+
if (rule.max != null && str.length > rule.max) {
|
|
49
|
+
return rule.message || `${item.label}最多 ${rule.max} 个字符`;
|
|
50
|
+
}
|
|
51
|
+
if (rule.pattern && !rule.pattern.test(str)) {
|
|
52
|
+
return rule.message || `${item.label}格式不正确`;
|
|
53
|
+
}
|
|
54
|
+
if (rule.validator) {
|
|
55
|
+
const r = await rule.validator(value, form);
|
|
56
|
+
if (r !== true)
|
|
57
|
+
return r || `${item.label}校验失败`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
export async function validateFormItems(items, form) {
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
if (isItemHidden(item, form))
|
|
65
|
+
continue;
|
|
66
|
+
const err = await validateItem(item, form);
|
|
67
|
+
if (err)
|
|
68
|
+
return err;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
export async function validateFormFields(items, form) {
|
|
73
|
+
const errors = {};
|
|
74
|
+
for (const item of items) {
|
|
75
|
+
if (isItemHidden(item, form))
|
|
76
|
+
continue;
|
|
77
|
+
const err = await validateItem(item, form);
|
|
78
|
+
if (err)
|
|
79
|
+
errors[item.prop] = err;
|
|
80
|
+
}
|
|
81
|
+
return errors;
|
|
82
|
+
}
|
|
83
|
+
export function applySetRules(items) {
|
|
84
|
+
return items.map((it) => {
|
|
85
|
+
if (!it.required || it.rules)
|
|
86
|
+
return it;
|
|
87
|
+
return {
|
|
88
|
+
...it,
|
|
89
|
+
rules: [{ required: true, message: `${it.label}不能为空` }]
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { useUserStore } from "../stores/user";
|
|
2
|
+
export const vPerm = {
|
|
3
|
+
mounted(el, binding) {
|
|
4
|
+
apply(el, binding.value);
|
|
5
|
+
},
|
|
6
|
+
updated(el, binding) {
|
|
7
|
+
apply(el, binding.value);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function apply(el, value) {
|
|
11
|
+
const user = useUserStore();
|
|
12
|
+
const ok = user.hasPerm(value);
|
|
13
|
+
el.style.display = ok ? "" : "none";
|
|
14
|
+
}
|
|
@@ -1 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
import { toast } from "vue-sonner";
|
|
2
|
+
import { request } from "../api/client";
|
|
3
|
+
import { extname, filename, pathJoin, uploadUid } from "../lib/upload";
|
|
4
|
+
const UPLOAD_PATH = "/admin/base/comm/upload";
|
|
5
|
+
const SIGN_SKIP = new Set([
|
|
6
|
+
"url",
|
|
7
|
+
"host",
|
|
8
|
+
"uploadUrl",
|
|
9
|
+
"publicDomain",
|
|
10
|
+
"previewUrl",
|
|
11
|
+
"preview"
|
|
12
|
+
]);
|
|
13
|
+
export function useUpload() {
|
|
14
|
+
async function toUpload(file, opts = {}) {
|
|
15
|
+
const { prefixPath = "app/base", onProgress } = opts;
|
|
16
|
+
const fileId = uploadUid();
|
|
17
|
+
const ext = extname(file.name);
|
|
18
|
+
const name = `${filename(file.name)}_${fileId}${ext ? `.${ext}` : ""}`;
|
|
19
|
+
const key = pathJoin(prefixPath, name);
|
|
20
|
+
const sign = await request(UPLOAD_PATH, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
body: JSON.stringify({})
|
|
23
|
+
});
|
|
24
|
+
const host = String(sign.host || sign.url || sign.uploadUrl || "");
|
|
25
|
+
if (!host)
|
|
26
|
+
throw new Error("上传地址无效");
|
|
27
|
+
const fd = new FormData;
|
|
28
|
+
fd.append("key", key);
|
|
29
|
+
for (const [k, v] of Object.entries(sign)) {
|
|
30
|
+
if (SIGN_SKIP.has(k) || v == null || fd.has(k))
|
|
31
|
+
continue;
|
|
32
|
+
fd.append(k, String(v));
|
|
33
|
+
}
|
|
34
|
+
fd.append("file", file);
|
|
35
|
+
await xhrUpload(host, fd, onProgress);
|
|
36
|
+
const preview = String(sign.publicDomain || sign.previewUrl || host);
|
|
37
|
+
return { url: pathJoin(preview, key), key, fileId };
|
|
38
|
+
}
|
|
39
|
+
return { toUpload };
|
|
40
|
+
}
|
|
41
|
+
function xhrUpload(url, body, onProgress) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const xhr = new XMLHttpRequest;
|
|
44
|
+
xhr.open("POST", url);
|
|
45
|
+
xhr.upload.onprogress = (e) => {
|
|
46
|
+
if (!e.lengthComputable)
|
|
47
|
+
return;
|
|
48
|
+
onProgress?.(Math.min(100, Math.floor(e.loaded / e.total * 100)));
|
|
49
|
+
};
|
|
50
|
+
xhr.onload = () => {
|
|
51
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
52
|
+
const msg = xhr.responseText || `上传失败 (${xhr.status})`;
|
|
53
|
+
toast.error(msg.slice(0, 200));
|
|
54
|
+
reject(new Error(msg));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
onProgress?.(100);
|
|
58
|
+
resolve();
|
|
59
|
+
};
|
|
60
|
+
xhr.onerror = () => reject(new Error("上传网络错误"));
|
|
61
|
+
xhr.send(body);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -1 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
export function getBrowser() {
|
|
2
|
+
const { clientHeight: height, clientWidth: width } = document.documentElement;
|
|
3
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
4
|
+
let type = (ua.match(/firefox|chrome|safari|opera/g) || ["other"])[0];
|
|
5
|
+
if ((ua.match(/msie|trident/g) || [])[0])
|
|
6
|
+
type = "msie";
|
|
7
|
+
let screen = "full";
|
|
8
|
+
if (width < 768)
|
|
9
|
+
screen = "xs";
|
|
10
|
+
else if (width < 992)
|
|
11
|
+
screen = "sm";
|
|
12
|
+
else if (width < 1200)
|
|
13
|
+
screen = "md";
|
|
14
|
+
else if (width < 1920)
|
|
15
|
+
screen = "xl";
|
|
16
|
+
const isPC = !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(ua);
|
|
17
|
+
return {
|
|
18
|
+
height,
|
|
19
|
+
width,
|
|
20
|
+
type,
|
|
21
|
+
screen,
|
|
22
|
+
isMini: screen === "xs" || !isPC
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/admin/lib/cn.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
export function cn(...inputs) {
|
|
4
|
+
return twMerge(clsx(inputs));
|
|
5
|
+
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
export const DIALOG_FLOAT_CLASS = "vm-dialog-float";
|
|
2
|
+
export function isDialogFloatTarget(event) {
|
|
3
|
+
const detail = event.detail;
|
|
4
|
+
const t = detail?.originalEvent?.target ?? event.target;
|
|
5
|
+
if (!t)
|
|
6
|
+
return false;
|
|
7
|
+
const el = t instanceof Element ? t : t.parentElement;
|
|
8
|
+
return Boolean(el?.closest(`.${DIALOG_FLOAT_CLASS}`));
|
|
9
|
+
}
|
|
10
|
+
export function preventDialogOutsideClose(event) {
|
|
11
|
+
if (isDialogFloatTarget(event))
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
}
|