@idooel/components 0.0.2-beta.5 → 0.0.2-beta.7
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/@idooel/components.esm.js +798 -335
- package/dist/@idooel/components.umd.js +798 -335
- package/package.json +1 -1
- package/packages/upload/src/index.vue +32 -8
|
@@ -2,411 +2,853 @@ import moment from 'moment';
|
|
|
2
2
|
import { type, net, route, util } from '@idooel/shared';
|
|
3
3
|
import FileUpload from 'vue-upload-component';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
var __defProp$2 = Object.defineProperty;
|
|
6
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value
|
|
11
|
+
}) : obj[key] = value;
|
|
12
|
+
var __publicField$2 = (obj, key, value) => {
|
|
13
|
+
__defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14
|
+
return value;
|
|
11
15
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"%": (data, a, b) => a(data) % b(data),
|
|
24
|
-
"===": (data, a, b) => a(data) === b(data),
|
|
25
|
-
"!==": (data, a, b) => a(data) !== b(data),
|
|
26
|
-
"==": (data, a, b) => a(data) == b(data),
|
|
27
|
-
"!=": (data, a, b) => a(data) != b(data),
|
|
28
|
-
"<": (data, a, b) => a(data) < b(data),
|
|
29
|
-
">": (data, a, b) => a(data) > b(data),
|
|
30
|
-
"<=": (data, a, b) => a(data) <= b(data),
|
|
31
|
-
">=": (data, a, b) => a(data) >= b(data),
|
|
32
|
-
"&&": (data, a, b) => a(data) && b(data),
|
|
33
|
-
"||": (data, a, b) => a(data) || b(data),
|
|
34
|
-
"!": (data, a) => !a(data)
|
|
35
|
-
};
|
|
36
|
-
function isNumber$1(char) {
|
|
37
|
-
return char >= "0" && char <= "9" && typeof char === "string";
|
|
16
|
+
class LexerError extends Error {
|
|
17
|
+
constructor(message, index, line, column) {
|
|
18
|
+
super(message);
|
|
19
|
+
__publicField$2(this, "index");
|
|
20
|
+
__publicField$2(this, "line");
|
|
21
|
+
__publicField$2(this, "column");
|
|
22
|
+
this.name = "LexerError";
|
|
23
|
+
this.index = index;
|
|
24
|
+
this.line = line;
|
|
25
|
+
this.column = column;
|
|
26
|
+
}
|
|
38
27
|
}
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
class ParseError extends Error {
|
|
29
|
+
constructor(message, index, line, column) {
|
|
30
|
+
super(message);
|
|
31
|
+
__publicField$2(this, "index");
|
|
32
|
+
__publicField$2(this, "line");
|
|
33
|
+
__publicField$2(this, "column");
|
|
34
|
+
this.name = "ParseError";
|
|
35
|
+
this.index = index;
|
|
36
|
+
this.line = line;
|
|
37
|
+
this.column = column;
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
class EvalError extends Error {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = "EvalError";
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
var TokenKind = /* @__PURE__ */(TokenKind2 => {
|
|
47
|
+
TokenKind2["Identifier"] = "Identifier";
|
|
48
|
+
TokenKind2["Number"] = "Number";
|
|
49
|
+
TokenKind2["String"] = "String";
|
|
50
|
+
TokenKind2["Punctuator"] = "Punctuator";
|
|
51
|
+
TokenKind2["Operator"] = "Operator";
|
|
52
|
+
TokenKind2["EOF"] = "EOF";
|
|
53
|
+
return TokenKind2;
|
|
54
|
+
})(TokenKind || {});
|
|
55
|
+
const PUNCTUATORS = /* @__PURE__ */new Set(["(", ")", "{", "}", "[", "]", ".", ",", ":", "?"]);
|
|
56
|
+
const MULTI_OPERATORS = ["?.", "??", "===", "!==", "==", "!=", "<=", ">=", "&&", "||"];
|
|
57
|
+
const SINGLE_OPERATORS = /* @__PURE__ */new Set(["+", "-", "*", "/", "%", "<", ">", "!"]);
|
|
58
|
+
function isDigit(ch) {
|
|
59
|
+
return ch >= "0" && ch <= "9";
|
|
60
|
+
}
|
|
61
|
+
function isIdentStart(ch) {
|
|
62
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$";
|
|
63
|
+
}
|
|
64
|
+
function isIdentPart(ch) {
|
|
65
|
+
return isIdentStart(ch) || isDigit(ch);
|
|
66
|
+
}
|
|
67
|
+
function lex(input) {
|
|
68
|
+
if (!input && input !== "") throw new LexerError("invalid input", 0, 1, 1);
|
|
69
|
+
const tokens = [];
|
|
70
|
+
let index = 0;
|
|
71
|
+
let line = 1;
|
|
72
|
+
let column = 1;
|
|
73
|
+
function current() {
|
|
74
|
+
return input.charAt(index);
|
|
75
|
+
}
|
|
76
|
+
function advance(n = 1) {
|
|
77
|
+
for (let i = 0; i < n; i++) {
|
|
78
|
+
const ch = input.charAt(index++);
|
|
79
|
+
if (ch === "\n") {
|
|
80
|
+
line++;
|
|
81
|
+
column = 1;
|
|
82
|
+
} else {
|
|
83
|
+
column++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function make(kind, text, value, startIndex, startLine, startColumn) {
|
|
88
|
+
return {
|
|
89
|
+
kind,
|
|
90
|
+
text,
|
|
91
|
+
value,
|
|
92
|
+
index: startIndex ?? index,
|
|
93
|
+
line: startLine ?? line,
|
|
94
|
+
column: startColumn ?? column
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
while (index < input.length) {
|
|
98
|
+
const ch = current();
|
|
99
|
+
if (ch === " " || ch === " " || ch === "\r" || ch === "\n" || ch === "\v" || ch === "\xA0") {
|
|
100
|
+
advance();
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === "'" || ch === '"') {
|
|
104
|
+
const quote = ch;
|
|
105
|
+
const startIndex = index;
|
|
106
|
+
const startLine = line;
|
|
107
|
+
const startColumn = column;
|
|
108
|
+
advance();
|
|
109
|
+
let value = "";
|
|
110
|
+
let escaped = false;
|
|
111
|
+
while (index < input.length) {
|
|
112
|
+
const c = current();
|
|
113
|
+
if (escaped) {
|
|
114
|
+
if (c === "u") {
|
|
115
|
+
const hex = input.substring(index + 1, index + 5);
|
|
116
|
+
if (!/^[\da-f]{4}$/i.test(hex)) {
|
|
117
|
+
throw new LexerError(`invalid unicode escape \\u${hex}`, index, line, column);
|
|
75
118
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
escape = true;
|
|
79
|
-
} else if (c === char) {
|
|
80
|
-
index++;
|
|
81
|
-
token = {
|
|
82
|
-
index: start,
|
|
83
|
-
constant: true,
|
|
84
|
-
text: char + value + char,
|
|
85
|
-
value
|
|
86
|
-
};
|
|
87
|
-
break;
|
|
119
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
120
|
+
advance(5);
|
|
88
121
|
} else {
|
|
89
|
-
|
|
122
|
+
const ESCAPE = {
|
|
123
|
+
n: "\n",
|
|
124
|
+
f: "\f",
|
|
125
|
+
r: "\r",
|
|
126
|
+
t: " ",
|
|
127
|
+
v: "\v"
|
|
128
|
+
};
|
|
129
|
+
value += ESCAPE[c] ?? c;
|
|
130
|
+
advance();
|
|
90
131
|
}
|
|
91
|
-
|
|
132
|
+
escaped = false;
|
|
133
|
+
continue;
|
|
92
134
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
135
|
+
if (c === "\\") {
|
|
136
|
+
escaped = true;
|
|
137
|
+
advance();
|
|
138
|
+
continue;
|
|
97
139
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (c === "." || isNumber$1(c)) {
|
|
104
|
-
value += c;
|
|
105
|
-
} else {
|
|
106
|
-
let c2 = content.charAt(index + 1);
|
|
107
|
-
if (c === "e" && isExpOperator(c2)) {
|
|
108
|
-
value += c;
|
|
109
|
-
} else if (isExpOperator(c) && c2 && isNumber$1(c2) && value.charAt(value.length - 1) === "e") {
|
|
110
|
-
value += c;
|
|
111
|
-
} else if (isExpOperator(c) && (!c2 || !isNumber$1(c2)) && value.charAt(value.length - 1) == "e") {
|
|
112
|
-
throw new Error(`invalid expression: ${content}`);
|
|
113
|
-
} else {
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
index++;
|
|
140
|
+
if (c === quote) {
|
|
141
|
+
advance();
|
|
142
|
+
tokens.push(make("String" /* String */, input.slice(startIndex, index), value, startIndex, startLine, startColumn));
|
|
143
|
+
value = "";
|
|
144
|
+
break;
|
|
118
145
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
let start = index;
|
|
127
|
-
while (index < length) {
|
|
128
|
-
let c = content.charAt(index);
|
|
129
|
-
if (!(isIdent(c) || isNumber$1(c))) {
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
index++;
|
|
146
|
+
value += c;
|
|
147
|
+
advance();
|
|
148
|
+
}
|
|
149
|
+
if (value !== "") {
|
|
150
|
+
const last = tokens[tokens.length - 1];
|
|
151
|
+
if (!last || last.index !== startIndex) {
|
|
152
|
+
throw new LexerError("unterminated string", startIndex, startLine, startColumn);
|
|
133
153
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
index++;
|
|
147
|
-
} else {
|
|
148
|
-
let char2 = char + content.charAt(index + 1);
|
|
149
|
-
let char3 = char2 + content.charAt(index + 2);
|
|
150
|
-
let op1 = OPERATORS[char];
|
|
151
|
-
let op2 = OPERATORS[char2];
|
|
152
|
-
let op3 = OPERATORS[char3];
|
|
153
|
-
if (op1 || op2 || op3) {
|
|
154
|
-
let text = op3 ? char3 : op2 ? char2 : char;
|
|
155
|
-
tokens.push({
|
|
156
|
-
index,
|
|
157
|
-
text,
|
|
158
|
-
operator: true
|
|
159
|
-
});
|
|
160
|
-
index += text.length;
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (isDigit(ch) || ch === "." && isDigit(input.charAt(index + 1))) {
|
|
158
|
+
const startIndex = index;
|
|
159
|
+
const startLine = line;
|
|
160
|
+
const startColumn = column;
|
|
161
|
+
let text = "";
|
|
162
|
+
while (index < input.length) {
|
|
163
|
+
let c = input.charAt(index).toLowerCase();
|
|
164
|
+
if (c === "." || isDigit(c)) {
|
|
165
|
+
text += c;
|
|
161
166
|
} else {
|
|
162
|
-
|
|
167
|
+
const c2 = input.charAt(index + 1);
|
|
168
|
+
if (c === "e" && (c2 === "+" || c2 === "-" || isDigit(c2))) {
|
|
169
|
+
text += c;
|
|
170
|
+
} else if ((c === "+" || c === "-") && isDigit(c2) && text.charAt(text.length - 1) === "e") {
|
|
171
|
+
text += c;
|
|
172
|
+
} else if ((c === "+" || c === "-") && (!c2 || !isDigit(c2)) && text.charAt(text.length - 1) === "e") {
|
|
173
|
+
throw new LexerError("dangling exponent", index, line, column);
|
|
174
|
+
} else {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
163
177
|
}
|
|
178
|
+
advance();
|
|
179
|
+
}
|
|
180
|
+
tokens.push(make("Number" /* Number */, text, Number(text), startIndex, startLine, startColumn));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (isIdentStart(ch)) {
|
|
184
|
+
const startIndex = index;
|
|
185
|
+
const startLine = line;
|
|
186
|
+
const startColumn = column;
|
|
187
|
+
advance();
|
|
188
|
+
while (index < input.length && isIdentPart(current())) advance();
|
|
189
|
+
const text = input.slice(startIndex, index);
|
|
190
|
+
tokens.push(make("Identifier" /* Identifier */, text, void 0, startIndex, startLine, startColumn));
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const three = input.substring(index, index + 3);
|
|
194
|
+
const two = input.substring(index, index + 2);
|
|
195
|
+
const one = input.substring(index, index + 1);
|
|
196
|
+
const multi = MULTI_OPERATORS.find(op => input.startsWith(op, index));
|
|
197
|
+
if (multi) {
|
|
198
|
+
tokens.push(make("Operator" /* Operator */, multi, void 0, index, line, column));
|
|
199
|
+
advance(multi.length);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (PUNCTUATORS.has(one)) {
|
|
203
|
+
tokens.push(make("Punctuator" /* Punctuator */, one, void 0, index, line, column));
|
|
204
|
+
advance();
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (SINGLE_OPERATORS.has(one)) {
|
|
208
|
+
tokens.push(make("Operator" /* Operator */, one, void 0, index, line, column));
|
|
209
|
+
advance();
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
throw new LexerError(`invalid token '${three || two || one}'`, index, line, column);
|
|
213
|
+
}
|
|
214
|
+
tokens.push({
|
|
215
|
+
kind: "EOF" /* EOF */,
|
|
216
|
+
text: "<eof>",
|
|
217
|
+
index,
|
|
218
|
+
line,
|
|
219
|
+
column
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
tokens
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
var __defProp$1 = Object.defineProperty;
|
|
226
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
|
|
227
|
+
enumerable: true,
|
|
228
|
+
configurable: true,
|
|
229
|
+
writable: true,
|
|
230
|
+
value
|
|
231
|
+
}) : obj[key] = value;
|
|
232
|
+
var __publicField$1 = (obj, key, value) => {
|
|
233
|
+
__defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
234
|
+
return value;
|
|
235
|
+
};
|
|
236
|
+
class LRUCache {
|
|
237
|
+
constructor(maxSize = 100) {
|
|
238
|
+
__publicField$1(this, "cache", /* @__PURE__ */new Map());
|
|
239
|
+
__publicField$1(this, "maxSize");
|
|
240
|
+
this.maxSize = maxSize;
|
|
241
|
+
}
|
|
242
|
+
get(key) {
|
|
243
|
+
const value = this.cache.get(key);
|
|
244
|
+
if (value !== void 0) {
|
|
245
|
+
this.cache.delete(key);
|
|
246
|
+
this.cache.set(key, value);
|
|
247
|
+
}
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
set(key, value) {
|
|
251
|
+
if (this.cache.has(key)) {
|
|
252
|
+
this.cache.delete(key);
|
|
253
|
+
}
|
|
254
|
+
if (this.cache.size >= this.maxSize) {
|
|
255
|
+
const firstKey = this.cache.keys().next().value;
|
|
256
|
+
if (firstKey !== void 0) {
|
|
257
|
+
this.cache.delete(firstKey);
|
|
164
258
|
}
|
|
165
259
|
}
|
|
260
|
+
this.cache.set(key, value);
|
|
261
|
+
}
|
|
262
|
+
clear() {
|
|
263
|
+
this.cache.clear();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const astCache = new LRUCache(100);
|
|
267
|
+
function parseToAst(input) {
|
|
268
|
+
const cached = astCache.get(input);
|
|
269
|
+
if (cached !== void 0) {
|
|
270
|
+
return cached;
|
|
271
|
+
}
|
|
272
|
+
const {
|
|
273
|
+
tokens
|
|
274
|
+
} = lex(input);
|
|
275
|
+
const state = new Parser(tokens, input);
|
|
276
|
+
const result = state.parse();
|
|
277
|
+
astCache.set(input, result);
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
class Parser {
|
|
281
|
+
constructor(tokens, input) {
|
|
282
|
+
__publicField$1(this, "tokens");
|
|
283
|
+
__publicField$1(this, "input");
|
|
284
|
+
__publicField$1(this, "i", 0);
|
|
166
285
|
this.tokens = tokens;
|
|
167
|
-
|
|
286
|
+
this.input = input;
|
|
168
287
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (tokens.length > 0 && text !== "}" && text !== ")" && text !== "]") {
|
|
175
|
-
func = this.expression();
|
|
176
|
-
}
|
|
177
|
-
return data => func && func(data);
|
|
288
|
+
current() {
|
|
289
|
+
return this.tokens[this.i];
|
|
290
|
+
}
|
|
291
|
+
next() {
|
|
292
|
+
return this.tokens[this.i + 1];
|
|
178
293
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
294
|
+
eat() {
|
|
295
|
+
return this.tokens[this.i++];
|
|
296
|
+
}
|
|
297
|
+
matchText(text) {
|
|
298
|
+
return this.current()?.text === text;
|
|
299
|
+
}
|
|
300
|
+
expectText(text) {
|
|
301
|
+
const t = this.current();
|
|
302
|
+
if (!text || t?.text === text) {
|
|
303
|
+
this.i++;
|
|
304
|
+
return t;
|
|
184
305
|
}
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
consumeText(text) {
|
|
309
|
+
const t = this.expectText(text);
|
|
310
|
+
if (!t) this.error(this.current(), `unexpected token, expect '${text ?? "<any>"}'`);
|
|
311
|
+
return t;
|
|
185
312
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
313
|
+
error(t, message) {
|
|
314
|
+
throw new ParseError(`${message} at ${t.line}:${t.column}`, t.index, t.line, t.column);
|
|
315
|
+
}
|
|
316
|
+
parse() {
|
|
317
|
+
const t = this.current();
|
|
318
|
+
if (t && t.kind !== TokenKind.EOF && t.text !== "}" && t.text !== ")" && t.text !== "]") {
|
|
319
|
+
const expr = this.expression();
|
|
320
|
+
return expr;
|
|
321
|
+
}
|
|
322
|
+
return void 0;
|
|
191
323
|
}
|
|
192
324
|
expression() {
|
|
193
325
|
return this.ternary();
|
|
194
326
|
}
|
|
195
327
|
ternary() {
|
|
196
|
-
|
|
197
|
-
if (this.
|
|
198
|
-
|
|
199
|
-
this.
|
|
200
|
-
|
|
201
|
-
|
|
328
|
+
const test = this.nullish();
|
|
329
|
+
if (this.expectText("?")) {
|
|
330
|
+
const consequent = this.expression();
|
|
331
|
+
this.consumeText(":");
|
|
332
|
+
const alternate = this.expression();
|
|
333
|
+
const node = {
|
|
334
|
+
type: "ConditionalExpression",
|
|
335
|
+
test,
|
|
336
|
+
consequent,
|
|
337
|
+
alternate
|
|
338
|
+
};
|
|
339
|
+
return node;
|
|
202
340
|
}
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
binary(left, op, right) {
|
|
206
|
-
let fn = OPERATORS[op];
|
|
207
|
-
return data => fn(data, left, right);
|
|
341
|
+
return test;
|
|
208
342
|
}
|
|
209
|
-
|
|
210
|
-
let
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
343
|
+
nullish() {
|
|
344
|
+
let left = this.logicalOR();
|
|
345
|
+
while (this.expectText("??")) {
|
|
346
|
+
if (isLogicalAndOr(left)) {
|
|
347
|
+
this.error(this.current(), "Cannot mix ?? with && or || without parentheses");
|
|
348
|
+
}
|
|
349
|
+
const rightStart = this.current();
|
|
350
|
+
const right = this.logicalOR();
|
|
351
|
+
if (isLogicalAndOr(right)) {
|
|
352
|
+
this.error(rightStart, "Cannot mix ?? with && or || without parentheses");
|
|
353
|
+
}
|
|
354
|
+
const node = {
|
|
355
|
+
type: "LogicalExpression",
|
|
356
|
+
operator: "??",
|
|
357
|
+
left,
|
|
358
|
+
right
|
|
359
|
+
};
|
|
360
|
+
left = node;
|
|
221
361
|
}
|
|
362
|
+
return left;
|
|
222
363
|
}
|
|
223
364
|
logicalOR() {
|
|
224
365
|
let left = this.logicalAND();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
366
|
+
while (this.expectText("||")) {
|
|
367
|
+
const right = this.logicalAND();
|
|
368
|
+
const node = {
|
|
369
|
+
type: "LogicalExpression",
|
|
370
|
+
operator: "||",
|
|
371
|
+
left,
|
|
372
|
+
right
|
|
373
|
+
};
|
|
374
|
+
left = node;
|
|
228
375
|
}
|
|
229
376
|
return left;
|
|
230
377
|
}
|
|
231
378
|
logicalAND() {
|
|
232
379
|
let left = this.equality();
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
380
|
+
while (this.expectText("&&")) {
|
|
381
|
+
const right = this.equality();
|
|
382
|
+
const node = {
|
|
383
|
+
type: "LogicalExpression",
|
|
384
|
+
operator: "&&",
|
|
385
|
+
left,
|
|
386
|
+
right
|
|
387
|
+
};
|
|
388
|
+
left = node;
|
|
236
389
|
}
|
|
237
390
|
return left;
|
|
238
391
|
}
|
|
239
392
|
equality() {
|
|
240
393
|
let left = this.relational();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
394
|
+
while (true) {
|
|
395
|
+
if (this.expectText("===")) {
|
|
396
|
+
left = this.binary(left, "===", this.relational());
|
|
397
|
+
} else if (this.expectText("!==")) {
|
|
398
|
+
left = this.binary(left, "!==", this.relational());
|
|
399
|
+
} else if (this.expectText("==")) {
|
|
400
|
+
left = this.binary(left, "==", this.relational());
|
|
401
|
+
} else if (this.expectText("!=")) {
|
|
402
|
+
left = this.binary(left, "!=", this.relational());
|
|
403
|
+
} else break;
|
|
244
404
|
}
|
|
245
405
|
return left;
|
|
246
406
|
}
|
|
247
407
|
relational() {
|
|
248
408
|
let left = this.additive();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
409
|
+
while (true) {
|
|
410
|
+
if (this.expectText("<=")) {
|
|
411
|
+
left = this.binary(left, "<=", this.additive());
|
|
412
|
+
} else if (this.expectText(">=")) {
|
|
413
|
+
left = this.binary(left, ">=", this.additive());
|
|
414
|
+
} else if (this.expectText("<")) {
|
|
415
|
+
left = this.binary(left, "<", this.additive());
|
|
416
|
+
} else if (this.expectText(">")) {
|
|
417
|
+
left = this.binary(left, ">", this.additive());
|
|
418
|
+
} else break;
|
|
252
419
|
}
|
|
253
420
|
return left;
|
|
254
421
|
}
|
|
255
422
|
additive() {
|
|
256
423
|
let left = this.multiplicative();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
424
|
+
while (true) {
|
|
425
|
+
if (this.expectText("+")) {
|
|
426
|
+
left = this.binary(left, "+", this.multiplicative());
|
|
427
|
+
} else if (this.expectText("-")) {
|
|
428
|
+
left = this.binary(left, "-", this.multiplicative());
|
|
429
|
+
} else break;
|
|
260
430
|
}
|
|
261
431
|
return left;
|
|
262
432
|
}
|
|
263
433
|
multiplicative() {
|
|
264
434
|
let left = this.unary();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
435
|
+
while (true) {
|
|
436
|
+
if (this.expectText("*")) {
|
|
437
|
+
left = this.binary(left, "*", this.unary());
|
|
438
|
+
} else if (this.expectText("/")) {
|
|
439
|
+
left = this.binary(left, "/", this.unary());
|
|
440
|
+
} else if (this.expectText("%")) {
|
|
441
|
+
left = this.binary(left, "%", this.unary());
|
|
442
|
+
} else break;
|
|
268
443
|
}
|
|
269
444
|
return left;
|
|
270
445
|
}
|
|
446
|
+
unary() {
|
|
447
|
+
if (this.expectText("+")) {
|
|
448
|
+
return this.primary();
|
|
449
|
+
}
|
|
450
|
+
if (this.expectText("-")) {
|
|
451
|
+
const arg = this.unary();
|
|
452
|
+
const node = {
|
|
453
|
+
type: "UnaryExpression",
|
|
454
|
+
operator: "-",
|
|
455
|
+
argument: arg
|
|
456
|
+
};
|
|
457
|
+
return node;
|
|
458
|
+
}
|
|
459
|
+
if (this.expectText("!")) {
|
|
460
|
+
const arg = this.unary();
|
|
461
|
+
const node = {
|
|
462
|
+
type: "UnaryExpression",
|
|
463
|
+
operator: "!",
|
|
464
|
+
argument: arg
|
|
465
|
+
};
|
|
466
|
+
return node;
|
|
467
|
+
}
|
|
468
|
+
return this.primary();
|
|
469
|
+
}
|
|
271
470
|
primary() {
|
|
272
|
-
|
|
273
|
-
let
|
|
274
|
-
if (this.
|
|
275
|
-
|
|
276
|
-
this.
|
|
277
|
-
} else if (this.
|
|
278
|
-
|
|
279
|
-
} else if (this.
|
|
280
|
-
|
|
281
|
-
} else if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
471
|
+
const t = this.current();
|
|
472
|
+
let expr;
|
|
473
|
+
if (this.expectText("(")) {
|
|
474
|
+
expr = this.expression();
|
|
475
|
+
this.consumeText(")");
|
|
476
|
+
} else if (this.expectText("[")) {
|
|
477
|
+
expr = this.array();
|
|
478
|
+
} else if (this.expectText("{")) {
|
|
479
|
+
expr = this.object();
|
|
480
|
+
} else if (t.kind === TokenKind.Identifier && (t.text === "true" || t.text === "false" || t.text === "null" || t.text === "undefined")) {
|
|
481
|
+
const tok = this.eat();
|
|
482
|
+
const map = {
|
|
483
|
+
true: true,
|
|
484
|
+
false: false,
|
|
485
|
+
null: null,
|
|
486
|
+
undefined: void 0
|
|
487
|
+
};
|
|
488
|
+
const node = {
|
|
489
|
+
type: "Literal",
|
|
490
|
+
value: map[tok.text]
|
|
491
|
+
};
|
|
492
|
+
expr = node;
|
|
493
|
+
} else if (t.kind === TokenKind.Identifier) {
|
|
494
|
+
expr = this.identifier();
|
|
495
|
+
} else if (t.kind === TokenKind.Number || t.kind === TokenKind.String) {
|
|
496
|
+
const tok = this.eat();
|
|
497
|
+
const node = {
|
|
498
|
+
type: "Literal",
|
|
499
|
+
value: tok.kind === TokenKind.Number ? Number(tok.text) : String(tok.value)
|
|
500
|
+
};
|
|
501
|
+
expr = node;
|
|
287
502
|
} else {
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
503
|
+
this.error(t, "unexpected token in primary");
|
|
504
|
+
}
|
|
505
|
+
while (true) {
|
|
506
|
+
if (this.expectText("(")) {
|
|
507
|
+
const args = [];
|
|
508
|
+
if (!this.matchText(")")) {
|
|
509
|
+
do {
|
|
510
|
+
args.push(this.expression());
|
|
511
|
+
} while (this.expectText(","));
|
|
512
|
+
}
|
|
513
|
+
this.consumeText(")");
|
|
514
|
+
const call = {
|
|
515
|
+
type: "CallExpression",
|
|
516
|
+
callee: expr,
|
|
517
|
+
arguments: args,
|
|
518
|
+
optional: false
|
|
519
|
+
};
|
|
520
|
+
expr = call;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (this.expectText("?.(")) ;
|
|
524
|
+
if (this.expectText(".")) {
|
|
525
|
+
const id = this.consumeIdentifier();
|
|
526
|
+
const prop = {
|
|
527
|
+
type: "Identifier",
|
|
528
|
+
name: id
|
|
529
|
+
};
|
|
530
|
+
const mem = {
|
|
531
|
+
type: "MemberExpression",
|
|
532
|
+
object: expr,
|
|
533
|
+
property: prop,
|
|
534
|
+
computed: false,
|
|
535
|
+
optional: false
|
|
536
|
+
};
|
|
537
|
+
expr = mem;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (this.expectText("[")) {
|
|
541
|
+
const prop = this.expression();
|
|
542
|
+
this.consumeText("]");
|
|
543
|
+
const mem = {
|
|
544
|
+
type: "MemberExpression",
|
|
545
|
+
object: expr,
|
|
546
|
+
property: prop,
|
|
547
|
+
computed: true,
|
|
548
|
+
optional: false
|
|
549
|
+
};
|
|
550
|
+
expr = mem;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (this.matchText("?.")) {
|
|
554
|
+
this.eat();
|
|
555
|
+
if (this.expectText("(")) {
|
|
556
|
+
const args = [];
|
|
557
|
+
if (!this.matchText(")")) {
|
|
558
|
+
do {
|
|
559
|
+
args.push(this.expression());
|
|
560
|
+
} while (this.expectText(","));
|
|
561
|
+
}
|
|
562
|
+
this.consumeText(")");
|
|
563
|
+
const call = {
|
|
564
|
+
type: "CallExpression",
|
|
565
|
+
callee: expr,
|
|
566
|
+
arguments: args,
|
|
567
|
+
optional: true
|
|
568
|
+
};
|
|
569
|
+
expr = call;
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (this.expectText("[")) {
|
|
573
|
+
const prop2 = this.expression();
|
|
574
|
+
this.consumeText("]");
|
|
575
|
+
const mem2 = {
|
|
576
|
+
type: "MemberExpression",
|
|
577
|
+
object: expr,
|
|
578
|
+
property: prop2,
|
|
579
|
+
computed: true,
|
|
580
|
+
optional: true
|
|
581
|
+
};
|
|
582
|
+
expr = mem2;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
const id = this.consumeIdentifier();
|
|
586
|
+
const prop = {
|
|
587
|
+
type: "Identifier",
|
|
588
|
+
name: id
|
|
589
|
+
};
|
|
590
|
+
const mem = {
|
|
591
|
+
type: "MemberExpression",
|
|
592
|
+
object: expr,
|
|
593
|
+
property: prop,
|
|
594
|
+
computed: false,
|
|
595
|
+
optional: true
|
|
596
|
+
};
|
|
597
|
+
expr = mem;
|
|
598
|
+
continue;
|
|
302
599
|
}
|
|
600
|
+
break;
|
|
303
601
|
}
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
fieldAccess(object) {
|
|
307
|
-
let getter = this.identifier();
|
|
308
|
-
return data => {
|
|
309
|
-
let o = object(data);
|
|
310
|
-
return o && getter(o);
|
|
311
|
-
};
|
|
602
|
+
return expr;
|
|
312
603
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
let key = indexFn(data) + "";
|
|
319
|
-
return o && o[key];
|
|
604
|
+
identifier() {
|
|
605
|
+
const name = this.consumeIdentifier();
|
|
606
|
+
const node = {
|
|
607
|
+
type: "Identifier",
|
|
608
|
+
name
|
|
320
609
|
};
|
|
610
|
+
return node;
|
|
321
611
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
} while (this.expect(","));
|
|
328
|
-
}
|
|
329
|
-
this.consume(")");
|
|
330
|
-
return data => {
|
|
331
|
-
let callContext = context && context(data);
|
|
332
|
-
let fn = func(data, callContext);
|
|
333
|
-
return fn && fn.apply(callContext, args.length ? args.map(arg => arg(data)) : null);
|
|
334
|
-
};
|
|
612
|
+
consumeIdentifier() {
|
|
613
|
+
const t = this.current();
|
|
614
|
+
if (t.kind !== TokenKind.Identifier) this.error(t, "identifier expected");
|
|
615
|
+
this.eat();
|
|
616
|
+
return t.text;
|
|
335
617
|
}
|
|
336
618
|
array() {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (token.text !== "]") {
|
|
619
|
+
const elements = [];
|
|
620
|
+
if (!this.matchText("]")) {
|
|
340
621
|
do {
|
|
341
|
-
if (this.
|
|
622
|
+
if (this.matchText("]")) break;
|
|
342
623
|
elements.push(this.expression());
|
|
343
|
-
} while (this.
|
|
624
|
+
} while (this.expectText(","));
|
|
344
625
|
}
|
|
345
|
-
this.
|
|
346
|
-
return
|
|
626
|
+
this.consumeText("]");
|
|
627
|
+
return {
|
|
628
|
+
type: "ArrayExpression",
|
|
629
|
+
elements
|
|
630
|
+
};
|
|
347
631
|
}
|
|
348
632
|
object() {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
let token = this.tokens[0];
|
|
352
|
-
if (token.text !== "}") {
|
|
633
|
+
const properties = [];
|
|
634
|
+
if (!this.matchText("}")) {
|
|
353
635
|
do {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
values.push(this.expression());
|
|
366
|
-
} while (this.expect(","));
|
|
636
|
+
if (this.matchText("}")) break;
|
|
637
|
+
const keyTok = this.eat();
|
|
638
|
+
let key;
|
|
639
|
+
if (keyTok.kind === TokenKind.String) key = String(keyTok.value);else if (keyTok.kind === TokenKind.Identifier) key = keyTok.text;else this.error(keyTok, "invalid object key");
|
|
640
|
+
this.consumeText(":");
|
|
641
|
+
const value = this.expression();
|
|
642
|
+
properties.push({
|
|
643
|
+
key,
|
|
644
|
+
value
|
|
645
|
+
});
|
|
646
|
+
} while (this.expectText(","));
|
|
367
647
|
}
|
|
368
|
-
this.
|
|
369
|
-
return
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
object[keys[i]] = values[i](data);
|
|
373
|
-
}
|
|
374
|
-
return object;
|
|
648
|
+
this.consumeText("}");
|
|
649
|
+
return {
|
|
650
|
+
type: "ObjectExpression",
|
|
651
|
+
properties
|
|
375
652
|
};
|
|
376
653
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
id += this.consume().text + this.consume().text;
|
|
384
|
-
token = this.tokens[0];
|
|
385
|
-
token2 = this.tokens[1];
|
|
386
|
-
token3 = this.tokens[2];
|
|
387
|
-
}
|
|
388
|
-
return data => {
|
|
389
|
-
let elements = id.split(".");
|
|
390
|
-
let key;
|
|
391
|
-
for (let i = 0; elements.length > 1; i++) {
|
|
392
|
-
key = elements.shift();
|
|
393
|
-
data = data[key];
|
|
394
|
-
if (!data) break;
|
|
395
|
-
}
|
|
396
|
-
key = elements.shift();
|
|
397
|
-
return data && data[key];
|
|
654
|
+
binary(left, op, right) {
|
|
655
|
+
return {
|
|
656
|
+
type: "BinaryExpression",
|
|
657
|
+
operator: op,
|
|
658
|
+
left,
|
|
659
|
+
right
|
|
398
660
|
};
|
|
399
661
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
662
|
+
}
|
|
663
|
+
function isLogicalAndOr(node) {
|
|
664
|
+
return node.type === "LogicalExpression" && (node.operator === "&&" || node.operator === "||");
|
|
665
|
+
}
|
|
666
|
+
const DENY_KEYS = /* @__PURE__ */new Set(["__proto__", "prototype", "constructor"]);
|
|
667
|
+
let evalOptions = {};
|
|
668
|
+
let currentDepth = 0;
|
|
669
|
+
function evaluate(ast, scope, options) {
|
|
670
|
+
if (!ast) return void 0;
|
|
671
|
+
evalOptions = options || {};
|
|
672
|
+
currentDepth = 0;
|
|
673
|
+
return exec(ast, scope);
|
|
674
|
+
}
|
|
675
|
+
function exec(node, scope) {
|
|
676
|
+
const maxDepth = evalOptions.maxDepth || 100;
|
|
677
|
+
if (++currentDepth > maxDepth) {
|
|
678
|
+
currentDepth--;
|
|
679
|
+
if (evalOptions.strict) {
|
|
680
|
+
throw new EvalError(`Maximum recursion depth (${maxDepth}) exceeded`);
|
|
681
|
+
}
|
|
682
|
+
return void 0;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
switch (node.type) {
|
|
686
|
+
case "Literal":
|
|
687
|
+
return node.value;
|
|
688
|
+
case "Identifier":
|
|
689
|
+
return readIdentifier(node, scope);
|
|
690
|
+
case "MemberExpression":
|
|
691
|
+
return readMember(node, scope);
|
|
692
|
+
case "CallExpression":
|
|
693
|
+
return callExpression(node, scope);
|
|
694
|
+
case "UnaryExpression":
|
|
695
|
+
return unaryExpression(node, scope);
|
|
696
|
+
case "BinaryExpression":
|
|
697
|
+
return binaryExpression(node, scope);
|
|
698
|
+
case "LogicalExpression":
|
|
699
|
+
return logicalExpression(node, scope);
|
|
700
|
+
case "ConditionalExpression":
|
|
701
|
+
return conditionalExpression(node, scope);
|
|
702
|
+
case "ArrayExpression":
|
|
703
|
+
return arrayExpression(node, scope);
|
|
704
|
+
case "ObjectExpression":
|
|
705
|
+
return objectExpression(node, scope);
|
|
706
|
+
default:
|
|
707
|
+
return void 0;
|
|
708
|
+
}
|
|
709
|
+
} finally {
|
|
710
|
+
currentDepth--;
|
|
403
711
|
}
|
|
404
712
|
}
|
|
405
|
-
|
|
713
|
+
function readIdentifier(node, scope) {
|
|
714
|
+
if (!scope) return void 0;
|
|
715
|
+
return scope[node.name];
|
|
716
|
+
}
|
|
717
|
+
function readMember(node, scope) {
|
|
718
|
+
const object = exec(node.object, scope);
|
|
719
|
+
if (object == null) {
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
const key = node.computed ? String(exec(node.property, scope)) : node.property.name;
|
|
723
|
+
if (DENY_KEYS.has(key)) {
|
|
724
|
+
if (evalOptions.strict) {
|
|
725
|
+
throw new EvalError(`Access to property "${key}" is not allowed`);
|
|
726
|
+
}
|
|
727
|
+
return void 0;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
return object[key];
|
|
731
|
+
} catch (err) {
|
|
732
|
+
if (evalOptions.strict) {
|
|
733
|
+
throw new EvalError(`Failed to access property "${key}": ${err}`);
|
|
734
|
+
}
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function callExpression(node, scope) {
|
|
739
|
+
let fn;
|
|
740
|
+
let thisArg = void 0;
|
|
741
|
+
const calleeNode = node.callee;
|
|
742
|
+
if (calleeNode.type === "MemberExpression") {
|
|
743
|
+
const mem = calleeNode;
|
|
744
|
+
thisArg = exec(mem.object, scope);
|
|
745
|
+
if (thisArg == null) return void 0;
|
|
746
|
+
const key = mem.computed ? String(exec(mem.property, scope)) : mem.property.name;
|
|
747
|
+
if (DENY_KEYS.has(key)) {
|
|
748
|
+
if (evalOptions.strict) {
|
|
749
|
+
throw new EvalError(`Call to method "${key}" is not allowed`);
|
|
750
|
+
}
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
fn = thisArg[key];
|
|
754
|
+
} else {
|
|
755
|
+
fn = exec(node.callee, scope);
|
|
756
|
+
}
|
|
757
|
+
if (fn == null) return void 0;
|
|
758
|
+
if (typeof fn !== "function") {
|
|
759
|
+
if (evalOptions.strict) {
|
|
760
|
+
throw new EvalError(`Cannot call non-function value`);
|
|
761
|
+
}
|
|
762
|
+
return void 0;
|
|
763
|
+
}
|
|
764
|
+
const args = node.arguments.map(a => exec(a, scope));
|
|
765
|
+
try {
|
|
766
|
+
return fn.apply(thisArg, args);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
if (evalOptions.strict) {
|
|
769
|
+
throw new EvalError(`Function call failed: ${err}`);
|
|
770
|
+
}
|
|
771
|
+
return void 0;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function unaryExpression(node, scope) {
|
|
775
|
+
const v = exec(node.argument, scope);
|
|
776
|
+
switch (node.operator) {
|
|
777
|
+
case "-":
|
|
778
|
+
return 0 - Number(v);
|
|
779
|
+
case "!":
|
|
780
|
+
return !v;
|
|
781
|
+
case "+":
|
|
782
|
+
return v;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function binaryExpression(node, scope) {
|
|
786
|
+
const a = exec(node.left, scope);
|
|
787
|
+
const b = exec(node.right, scope);
|
|
788
|
+
switch (node.operator) {
|
|
789
|
+
case "+":
|
|
790
|
+
return a + b;
|
|
791
|
+
case "-":
|
|
792
|
+
return a - b;
|
|
793
|
+
case "*":
|
|
794
|
+
return a * b;
|
|
795
|
+
case "/":
|
|
796
|
+
return a / b;
|
|
797
|
+
case "%":
|
|
798
|
+
return a % b;
|
|
799
|
+
case "==":
|
|
800
|
+
return a == b;
|
|
801
|
+
case "!=":
|
|
802
|
+
return a != b;
|
|
803
|
+
case "===":
|
|
804
|
+
return a === b;
|
|
805
|
+
case "!==":
|
|
806
|
+
return a !== b;
|
|
807
|
+
case "<":
|
|
808
|
+
return a < b;
|
|
809
|
+
case ">":
|
|
810
|
+
return a > b;
|
|
811
|
+
case "<=":
|
|
812
|
+
return a <= b;
|
|
813
|
+
case ">=":
|
|
814
|
+
return a >= b;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function logicalExpression(node, scope) {
|
|
818
|
+
if (node.operator === "&&") {
|
|
819
|
+
const left2 = exec(node.left, scope);
|
|
820
|
+
return left2 ? exec(node.right, scope) : left2;
|
|
821
|
+
}
|
|
822
|
+
if (node.operator === "||") {
|
|
823
|
+
const left2 = exec(node.left, scope);
|
|
824
|
+
return left2 ? left2 : exec(node.right, scope);
|
|
825
|
+
}
|
|
826
|
+
const left = exec(node.left, scope);
|
|
827
|
+
return left ?? exec(node.right, scope);
|
|
828
|
+
}
|
|
829
|
+
function conditionalExpression(node, scope) {
|
|
830
|
+
const test = exec(node.test, scope);
|
|
831
|
+
return test ? exec(node.consequent, scope) : exec(node.alternate, scope);
|
|
832
|
+
}
|
|
833
|
+
function arrayExpression(node, scope) {
|
|
834
|
+
return node.elements.map(e => exec(e, scope));
|
|
835
|
+
}
|
|
836
|
+
function objectExpression(node, scope) {
|
|
837
|
+
const out = {};
|
|
838
|
+
for (const p of node.properties) {
|
|
839
|
+
if (DENY_KEYS.has(p.key)) continue;
|
|
840
|
+
out[p.key] = exec(p.value, scope);
|
|
841
|
+
}
|
|
842
|
+
return out;
|
|
843
|
+
}
|
|
844
|
+
function compile(expression, options) {
|
|
406
845
|
if (!expression) throw new Error("expression is required");
|
|
407
|
-
const
|
|
408
|
-
return
|
|
409
|
-
}
|
|
846
|
+
const ast = parseToAst(expression);
|
|
847
|
+
return scope => evaluate(ast, scope, options);
|
|
848
|
+
}
|
|
849
|
+
function parse$1(expression, scope = {}, options) {
|
|
850
|
+
return compile(expression, options)(scope);
|
|
851
|
+
}
|
|
410
852
|
|
|
411
853
|
const CONTEXT = '__idooel__ele__context__';
|
|
412
854
|
const AREA_NAMES = {
|
|
@@ -5358,8 +5800,7 @@ var script$t = {
|
|
|
5358
5800
|
querys: {
|
|
5359
5801
|
type: Object,
|
|
5360
5802
|
default: () => ({
|
|
5361
|
-
_csrf: localStorage.getItem('token')
|
|
5362
|
-
_t: new Date().valueOf()
|
|
5803
|
+
_csrf: localStorage.getItem('token')
|
|
5363
5804
|
})
|
|
5364
5805
|
},
|
|
5365
5806
|
headers: {
|
|
@@ -5388,7 +5829,8 @@ var script$t = {
|
|
|
5388
5829
|
// 上传状态管理
|
|
5389
5830
|
saveToServerAsyncPageTimer: null,
|
|
5390
5831
|
uploadRefId: null,
|
|
5391
|
-
groupId
|
|
5832
|
+
// 多选且外部无值时,预先生成 groupId,确保首次上传参数完整
|
|
5833
|
+
groupId: this.multiple && !this.value ? v4() : null
|
|
5392
5834
|
};
|
|
5393
5835
|
},
|
|
5394
5836
|
created() {
|
|
@@ -5401,11 +5843,6 @@ var script$t = {
|
|
|
5401
5843
|
watch: {
|
|
5402
5844
|
value: {
|
|
5403
5845
|
async handler(value) {
|
|
5404
|
-
console.log('watch.value triggered:', {
|
|
5405
|
-
value,
|
|
5406
|
-
multiple: this.multiple,
|
|
5407
|
-
currentGroupId: this.groupId
|
|
5408
|
-
});
|
|
5409
5846
|
if (type.isEmpty(value)) {
|
|
5410
5847
|
this.resetFiles();
|
|
5411
5848
|
} else if (this.multiple) {
|
|
@@ -5779,10 +6216,26 @@ var script$t = {
|
|
|
5779
6216
|
// 激活上传组件
|
|
5780
6217
|
this.activateUploadComponent(newFile, oldFile);
|
|
5781
6218
|
},
|
|
6219
|
+
/**
|
|
6220
|
+
* 生成唯一的随机数
|
|
6221
|
+
*/
|
|
6222
|
+
generateUniqueRandom() {
|
|
6223
|
+
// 使用 crypto.getRandomValues 生成更安全的随机数
|
|
6224
|
+
if (window.crypto && window.crypto.getRandomValues) {
|
|
6225
|
+
const array = new Uint32Array(1);
|
|
6226
|
+
window.crypto.getRandomValues(array);
|
|
6227
|
+
return array[0].toString();
|
|
6228
|
+
}
|
|
6229
|
+
// 降级方案:使用 Math.random 结合时间戳
|
|
6230
|
+
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
|
|
6231
|
+
},
|
|
5782
6232
|
/**
|
|
5783
6233
|
* 处理文件添加
|
|
5784
6234
|
*/
|
|
5785
6235
|
handleFileAdd(newFile) {
|
|
6236
|
+
// 为每个文件生成唯一的随机数
|
|
6237
|
+
const uniqueRandom = this.generateUniqueRandom();
|
|
6238
|
+
newFile.postAction = newFile.postAction + '&_t=' + uniqueRandom;
|
|
5786
6239
|
console.log('add file:', newFile);
|
|
5787
6240
|
console.log('extensions:', this.extensions);
|
|
5788
6241
|
console.log('accept:', this.accept);
|
|
@@ -5884,8 +6337,18 @@ var script$t = {
|
|
|
5884
6337
|
resetFiles() {
|
|
5885
6338
|
this.files = [];
|
|
5886
6339
|
this.buildedFiles = [];
|
|
5887
|
-
|
|
5888
|
-
|
|
6340
|
+
// 多选模式下保留或生成 groupId,避免首次上传为空
|
|
6341
|
+
if (this.multiple) {
|
|
6342
|
+
if (!this.groupId) {
|
|
6343
|
+
this.groupId = v4();
|
|
6344
|
+
console.log('Generated groupId in resetFiles:', this.groupId);
|
|
6345
|
+
} else {
|
|
6346
|
+
console.log('Preserve existing groupId in resetFiles:', this.groupId);
|
|
6347
|
+
}
|
|
6348
|
+
} else {
|
|
6349
|
+
this.groupId = null;
|
|
6350
|
+
console.log('Reset groupId to null (single mode)');
|
|
6351
|
+
}
|
|
5889
6352
|
},
|
|
5890
6353
|
/**
|
|
5891
6354
|
* 处理多文件模式的值变化
|
|
@@ -6140,11 +6603,11 @@ __vue_render__$t._withStripped = true;
|
|
|
6140
6603
|
/* style */
|
|
6141
6604
|
const __vue_inject_styles__$t = function (inject) {
|
|
6142
6605
|
if (!inject) return
|
|
6143
|
-
inject("data-v-4772bfd1_0", { source: "[data-v-4772bfd1] .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n border-radius: var(--idooel-form-border-radius);\n}\n[data-v-4772bfd1] .ele-upload__inner:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n}\n.ele-upload__wrapper[data-v-4772bfd1] {\n width: 100%;\n}\n.ele-upload__wrapper .ele-upload__area[data-v-4772bfd1] {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon[data-v-4772bfd1] {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon-cloud-upload[data-v-4772bfd1] {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon[data-v-4772bfd1] {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text[data-v-4772bfd1] {\n margin-left: 16px;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__message[data-v-4772bfd1] {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__ext[data-v-4772bfd1] {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item[data-v-4772bfd1] {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__suffix--icon[data-v-4772bfd1] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name[data-v-4772bfd1] {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name .ele-file__inner[data-v-4772bfd1] {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete[data-v-4772bfd1] {\n margin-left: 8px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete .ele-file__delete--icon[data-v-4772bfd1] {\n margin-left: 8px;\n cursor: pointer;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["/Users/huangshan/Goldgov/GanJiao/base-elearning-frontend-model/packages/components/packages/upload/src/index.vue","index.vue"],"names":[],"mappings":"AA0tBA;EACA,qBAAA;EACA,eAAA;EACA,wDAAA;EACA,yDAAA;EAIA,+CAAA;AC5tBA;ADytBA;EACA,0DAAA;ACvtBA;AD2tBA;EACA,WAAA;ACxtBA;ADytBA;EACA,aAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,mBAAA;ACvtBA;ADwtBA;EACA,kCAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,cAAA;ACttBA;ADutBA;EACA,eAAA;EACA,kCAAA;ACrtBA;ADutBA;EACA,eAAA;EACA,kCAAA;ACrtBA;ADwtBA;EACA,iBAAA;ACttBA;ADutBA;EACA,eAAA;EACA,8BAAA;EACA,gBAAA;ACrtBA;ADutBA;EACA,gBAAA;EACA,eAAA;EACA,6BAAA;ACrtBA;AD0tBA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;EACA,+CAAA;EACA,8CAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;ACxtBA;ADytBA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;ACvtBA;ADytBA;EACA,OAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;ACvtBA;ADwtBA;EACA,gBAAA;EACA,uBAAA;ACttBA;ADytBA;EACA,gBAAA;ACvtBA;ADwtBA;EACA,gBAAA;EACA,eAAA;ACttBA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\n <div class=\"ele-upload__wrapper\">\n <FileUpload\n class=\"ele-upload__inner\"\n v-show=\"isShowUploadContainer\"\n v-model=\"files\"\n :ref=\"uploadRef\"\n :drop=\"drop\"\n :chunk-enabled=\"chunkEnabled\"\n :chunk=\"chunkConfig\"\n :accept=\"accept\"\n :size=\"fileSizeLimit\"\n :post-action=\"postAction\"\n :multiple=\"multiple\"\n :headers=\"headers\"\n :maximum=\"getMaximum\"\n :data=\"uploadParams\"\n @input-file=\"onWatchInputFiles\"\n @input=\"onWatchFiles\"\n style=\"width: 100%;\">\n <section class=\"ele-upload__area\">\n <div class=\"ele-upload__area--icon\">\n <template v-if=\"iconIsZhWrod\">\n {{ icon }}\n </template>\n <template v-else>\n <ele-icon :type=\"icon\"></ele-icon>\n </template>\n </div>\n <div class=\"ele-upload__area--text\">\n <div class=\"ele-upload__message\" v-if=\"message\" v-html=\"message\"></div>\n <div class=\"ele-upload__message\" v-else>单击或拖动文件到该区域以上传</div>\n <div class=\"ele-upload__ext\" v-if=\"ext\" v-html=\"ext\"></div>\n <div class=\"ele-upload__ext\" v-else>文件小于{{ size }}M</div>\n </div>\n </section>\n </FileUpload>\n <section class=\"ele-files__wrapper\">\n <!-- 显示正在上传的文件(有进度条) -->\n <div class=\"ele-file__item\" v-for=\"(file, idx) in uploadingFiles\" :key=\"`uploading-${idx}`\">\n <div class=\"ele-file__suffix--icon\">\n <ele-icon :type=\"fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'\"></ele-icon>\n </div>\n <div class=\"ele-file__name\">\n <div class=\"ele-file__inner\">{{ file.name }}</div>\n <div v-if=\"file.progress !== undefined\" class=\"ele-uplpad__progress\">\n <a-progress :strokeWidth=\"2\" :percent=\"Number(file.progress)\" size=\"small\" />\n </div>\n </div>\n <div class=\"ele-file__delete\" v-if=\"file.success || file.error\">\n <span class=\"ele-file__size\">{{ (file.size / byteConversion).toFixed(2) }}M</span>\n <span class=\"ele-file__delete--icon\" @click=\"handleClickDelete(file)\">\n <ele-icon type=\"delete\"></ele-icon>\n </span>\n </div>\n </div>\n \n <!-- 显示已上传完成的文件 -->\n <div class=\"ele-file__item\" v-for=\"(file, idx) in completedFiles\" :key=\"`completed-${idx}`\">\n <div class=\"ele-file__suffix--icon\">\n <ele-icon :type=\"fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'\"></ele-icon>\n </div>\n <div class=\"ele-file__name\">\n <div class=\"ele-file__inner\" @click=\"handleClickDownload(file)\">{{ file.name }}</div>\n </div>\n <div class=\"ele-file__delete\">\n <span class=\"ele-file__size\">{{ (file.size / byteConversion).toFixed(2) }}M</span>\n <span class=\"ele-file__delete--icon\" @click=\"handleClickDelete(file)\">\n <ele-icon type=\"delete\"></ele-icon>\n </span>\n </div>\n </div>\n </section>\n </div>\n</template>\n\n<script>\nimport FileUpload from 'vue-upload-component'\nimport { v4 as uuidv4 } from 'uuid'\nimport { route, net, type } from '@idooel/shared'\n\n// 常量定义\nconst CONSTANTS = {\n DEFAULT_URL: 'zuul/api-file/workbench/file',\n DEFAULT_ICON: '上传',\n DEFAULT_SIZE: 100,\n DEFAULT_MESSAGE: '单击或拖动文件到该区域以上传',\n DEFAULT_MAXIMUM: 20,\n BYTE_CONVERSION: 1024 * 1024,\n CHUNK_MIN_SIZE: 3 * 1024 * 1024,\n CHUNK_MAX_ACTIVE: 3,\n CHUNK_MAX_RETRIES: 5,\n SAVE_INTERVAL: 2000\n}\n\n// 文件后缀图标映射\nconst FILE_SUFFIX_ICONS = {\n 'doc': { name: 'icon-doc' },\n 'html': { name: 'icon-html' },\n 'mp4': { name: 'icon-mp' },\n 'pdf': { name: 'icon-pdf' },\n 'ppt': { name: 'icon-ppt' },\n 'psd': { name: 'icon-psd' },\n 'rtf': { name: 'icon-rtf' },\n 'txt': { name: 'icon-txt' },\n 'vis': { name: 'icon-vis' },\n 'xls': { name: 'icon-xls' },\n 'xml': { name: 'icon-xml' },\n 'zip': { name: 'icon-zip' },\n 'jpg': { name: 'icon-img' },\n 'mp3': { name: 'icon-mp1' }\n}\n\nexport default {\n name: 'ele-upload',\n components: {\n FileUpload\n },\n model: {\n prop: 'value',\n event: 'change'\n },\n props: {\n url: {\n type: String,\n default: CONSTANTS.DEFAULT_URL\n },\n icon: {\n type: String,\n default: CONSTANTS.DEFAULT_ICON\n },\n size: {\n type: Number,\n default: CONSTANTS.DEFAULT_SIZE\n },\n message: {\n type: String,\n default: CONSTANTS.DEFAULT_MESSAGE\n },\n ext: {\n type: String\n },\n extensions: {\n type: String\n },\n accept: {\n type: String\n },\n maximum: {\n type: Number,\n default: CONSTANTS.DEFAULT_MAXIMUM\n },\n multiple: {\n type: Boolean,\n default: false\n },\n drop: {\n type: Boolean,\n default: true\n },\n value: {\n type: [String, Array]\n },\n querys: {\n type: Object,\n default: () => ({\n _csrf: localStorage.getItem('token'),\n _t: new Date().valueOf()\n })\n },\n headers: {\n type: Object,\n default: () => ({\n 'X-XSRF-TOKEN': localStorage.getItem('token')\n })\n },\n byteConversion: {\n type: Number,\n default: CONSTANTS.BYTE_CONVERSION\n },\n chunkEnabled: {\n type: Boolean,\n default: true\n }\n },\n data() {\n return {\n // 文件状态管理\n files: [], // vue-upload-component 管理的文件\n buildedFiles: [], // 已构建完成的文件列表\n \n // 上传状态管理\n saveToServerAsyncPageTimer: null,\n uploadRefId: null,\n groupId: null\n }\n },\n created() {\n // 多文件模式下,如果没有外部 groupId,则生成一个\n if (this.multiple && !this.value) {\n this.groupId = uuidv4()\n console.log('Created with new groupId:', this.groupId)\n }\n },\n watch: {\n value: {\n async handler(value) {\n console.log('watch.value triggered:', { value, multiple: this.multiple, currentGroupId: this.groupId })\n \n if (type.isEmpty(value)) {\n this.resetFiles()\n } else if (this.multiple) {\n this.handleMultipleFileValue(value)\n } else {\n this.handleSingleFileValue()\n }\n },\n immediate: true\n }\n },\n computed: {\n // ==================== 基础配置 ====================\n prefixPath() {\n return window.prefixPath\n },\n \n uploadRef() {\n if (!this.uploadRefId) {\n this.uploadRefId = `uploadRef_${uuidv4()}`\n }\n return this.uploadRefId\n },\n \n iconIsZhWord() {\n return type.isZhWord(this.icon)\n },\n \n // ==================== 上传配置 ====================\n uploadParams() {\n return this.multiple ? { groupID: this.groupId } : {}\n },\n \n fileSizeLimit() {\n return this.size * this.byteConversion\n },\n \n postAction() {\n const queryString = route.toQueryString(this.querys)\n return `${this.prefixPath}${this.url}?${queryString}`\n },\n \n chunkConfig() {\n return {\n action: `${this.prefixPath}zuul/api-file/workbench/file/temp/chunk/vue`,\n headers: { ...this.headers },\n minSize: CONSTANTS.CHUNK_MIN_SIZE,\n maxActive: CONSTANTS.CHUNK_MAX_ACTIVE,\n maxRetries: CONSTANTS.CHUNK_MAX_RETRIES,\n startBody: { override: true, path: '/cw' },\n uploadBody: { override: true, path: '/cw' },\n finishBody: { override: true, path: '/cw' }\n }\n },\n \n getMaximum() {\n return this.multiple ? this.maximum : 1\n },\n \n // ==================== 文件状态 ====================\n uploadingFiles() {\n return this.files.filter(file => file.progress !== undefined && !file.success)\n },\n \n completedFiles() {\n return this.buildedFiles.filter(file => file.fileID)\n },\n \n totalFiles() {\n return this.uploadingFiles.length + this.completedFiles.length\n },\n \n isFileUploadSuccessed() {\n const currentUploadingFiles = this.files.filter(file => file.response !== undefined)\n if (currentUploadingFiles.length === 0) {\n return this.buildedFiles.length > 0\n }\n return currentUploadingFiles.every(file => file.success)\n },\n \n isShowUploadContainer() {\n const maxFiles = this.multiple ? this.maximum : 1\n return this.totalFiles < maxFiles\n },\n \n // ==================== 文件信息 ====================\n fileSuffixIcon() {\n return FILE_SUFFIX_ICONS\n },\n \n fileIds() {\n if (this.multiple) {\n return this.groupId\n } else {\n const fileIds = this.buildedFiles.map(file => file.fileID)\n return fileIds[0]\n }\n },\n \n fileResponseData() {\n return this.multiple ? this.buildedFiles : this.buildedFiles[0]\n }\n },\n methods: {\n // ==================== 文件管理 ====================\n \n /**\n * 从多个数组中移除文件\n */\n removeFromArrays(file, arrays, key = 'fileID') {\n return arrays.map(arr => \n arr.filter(item => item[key] !== file[key] && item.id !== file.id)\n )\n },\n \n /**\n * 检查文件是否为新文件\n */\n isNewFile(newFile, existingFiles, key = 'fileID') {\n return newFile[key] && !existingFiles.some(existing => existing[key] === newFile[key])\n },\n \n /**\n * 合并文件数据\n */\n mergeFileData(uploadFile) {\n return {\n ...uploadFile.response.data,\n ...uploadFile\n }\n },\n \n /**\n * 初始化文件列表\n */\n async initializeFiles() {\n if (!this.value) return\n \n if (this.multiple) {\n await this.fetchFilesWithGroupId()\n } else {\n await this.fetchFileWithFileId()\n }\n },\n \n /**\n * 获取多文件组\n */\n async fetchFilesWithGroupId() {\n try {\n const response = await net.get(`/api-file/workbench/file/group/${this.value}`)\n const data = response.data || []\n \n // 只有在没有现有文件时才设置初始文件列表\n if (this.buildedFiles.length === 0) {\n this.buildedFiles = data\n console.log('Initial files loaded:', this.buildedFiles.length)\n } else {\n console.log('Keep existing files, skip initial load')\n }\n } catch (error) {\n console.log('fetchFilesWithGroupId error:', error)\n console.log('Keep current files, do not clear due to API error')\n }\n },\n \n /**\n * 获取单文件\n */\n async fetchFileWithFileId() {\n try {\n const response = await net.get(`/api-file/file/${this.value}`)\n const data = response.data\n this.buildedFiles = [data]\n this.files = [data]\n } catch (error) {\n console.log('fetchFileWithFileId error:', error)\n }\n },\n \n /**\n * 处理文件删除\n */\n handleClickDelete(file) {\n const { fileID } = file\n console.log('Deleting file:', { name: file.name, fileID })\n \n // 从上传组件中移除文件\n if (this.$refs[this.uploadRef]) {\n this.$refs[this.uploadRef].remove(file)\n }\n \n // 从所有数组中移除文件\n [this.files, this.buildedFiles] = this.removeFromArrays(file, [this.files, this.buildedFiles])\n \n console.log('After deletion - files:', this.files.length, 'buildedFiles:', this.buildedFiles.length)\n \n // 多文件模式下,如果删除最后一个文件,重置 groupId\n if (this.multiple && this.buildedFiles.length === 0) {\n this.groupId = null\n console.log('Reset groupId after deleting last file')\n }\n \n // 触发 change 事件\n this.$emit('change', this.fileIds)\n },\n \n /**\n * 处理文件下载\n */\n handleClickDownload(file) {\n const { fileID: fileId } = file\n window.open(`/api-file/workbench/file/stream/${fileId}?origin=true`)\n },\n // ==================== 上传处理 ====================\n \n /**\n * 处理文件上传状态变化\n */\n onWatchFiles(files) {\n console.log('onWatchFiles called with files:', files.length)\n console.log('Current buildedFiles:', this.buildedFiles.length)\n \n // 更新文件状态\n this.files = files\n \n // 处理已上传成功的文件\n this.processUploadedFiles(files)\n \n // 检查上传是否完成\n if (this.isFileUploadSuccessed) {\n this.$emit('change', this.fileIds)\n this.$emit('on-success', this.fileResponseData)\n }\n },\n \n /**\n * 处理已上传成功的文件\n */\n processUploadedFiles(files) {\n // 处理所有有响应的文件(包括正在上传和已完成的)\n const uploadedFiles = files.filter(file => file.response)\n const newBuildedFiles = uploadedFiles.map(file => this.mergeFileData(file))\n \n if (this.multiple) {\n this.processMultipleFiles(newBuildedFiles)\n } else {\n // 单文件模式:只保留最新的文件\n this.buildedFiles = newBuildedFiles\n }\n \n this.logFileStatus()\n },\n \n /**\n * 处理多文件模式\n */\n processMultipleFiles(newBuildedFiles) {\n // 获取已存在的文件ID集合\n const existingFileIds = new Set(this.buildedFiles.map(f => f.fileID).filter(id => id))\n \n // 过滤出真正的新文件\n const trulyNewFiles = newBuildedFiles.filter(newFile => \n this.isNewFile(newFile, this.buildedFiles)\n )\n \n console.log('Existing fileIDs:', Array.from(existingFileIds))\n console.log('New uploaded files:', newBuildedFiles.map(f => ({ name: f.name, fileID: f.fileID })))\n console.log('Truly new files:', trulyNewFiles.map(f => ({ name: f.name, fileID: f.fileID })))\n \n // 将新文件追加到现有文件列表\n if (trulyNewFiles.length > 0) {\n this.buildedFiles = [...this.buildedFiles, ...trulyNewFiles]\n console.log('Added new files, total buildedFiles:', this.buildedFiles.length)\n }\n \n // 更新现有文件的状态(包括新添加的文件)\n this.updateExistingFiles(newBuildedFiles)\n },\n \n /**\n * 更新现有文件状态\n */\n updateExistingFiles(newBuildedFiles) {\n this.buildedFiles = this.buildedFiles.map(existingFile => {\n const updatedFile = newBuildedFiles.find(newFile => newFile.fileID === existingFile.fileID)\n return updatedFile ? { ...existingFile, ...updatedFile } : existingFile\n })\n },\n \n /**\n * 记录文件状态日志\n */\n logFileStatus() {\n console.log('Final buildedFiles:', this.buildedFiles.length)\n console.log('buildedFiles details:', this.buildedFiles.map(f => ({ name: f.name, fileID: f.fileID, success: f.success })))\n console.log('Uploading files:', this.uploadingFiles.length)\n console.log('Completed files:', this.completedFiles.length)\n },\n // ==================== 异步处理 ====================\n \n /**\n * 异步保存文件到服务器\n */\n async saveToServerAsyncPage(payloads = {}) {\n try {\n const response = await net.post('zuul/api-file/workbench/file/temp/saveToServerAsyncPage', payloads, { \n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'\n }\n })\n \n const { data } = response\n if (data !== 'saveToServerAsyncPage') {\n clearInterval(this.saveToServerAsyncPageTimer)\n }\n } catch (error) {\n console.error('saveToServerAsyncPage error:', error)\n clearInterval(this.saveToServerAsyncPageTimer)\n }\n },\n \n // ==================== 文件验证 ====================\n \n /**\n * 验证文件类型\n */\n validateFileType(file) {\n if (!file || !file.name) {\n console.log('文件或文件名不存在')\n return false\n }\n \n const fileExt = this.getFileExtension(file.name)\n console.log('文件扩展名:', fileExt)\n \n if (this.extensions) {\n const allowedExts = this.getAllowedExtensions()\n console.log('允许的扩展名:', allowedExts)\n \n if (!allowedExts.includes(fileExt)) {\n console.log('扩展名不在允许列表中')\n this.$message.error(`不支持的文件类型 \"${fileExt}\",请上传 ${this.extensions} 格式的文件`)\n return false\n }\n }\n \n console.log('文件类型验证通过')\n return true\n },\n \n /**\n * 获取文件扩展名\n */\n getFileExtension(fileName) {\n return fileName.toLowerCase().substring(fileName.lastIndexOf('.'))\n },\n \n /**\n * 获取允许的扩展名列表\n */\n getAllowedExtensions() {\n return this.extensions.toLowerCase()\n .split(',')\n .map(ext => ext.trim().startsWith('.') ? ext.trim() : `.${ext.trim()}`)\n },\n // ==================== 事件处理 ====================\n \n /**\n * 处理文件输入事件\n */\n onWatchInputFiles(newFile, oldFile) {\n if (newFile && !oldFile) {\n this.handleFileAdd(newFile)\n } else if (newFile && oldFile) {\n this.handleFileUpdate(newFile)\n } else if (!newFile && oldFile) {\n this.handleFileDelete()\n }\n \n // 激活上传组件\n this.activateUploadComponent(newFile, oldFile)\n },\n \n /**\n * 处理文件添加\n */\n handleFileAdd(newFile) {\n console.log('add file:', newFile)\n console.log('extensions:', this.extensions)\n console.log('accept:', this.accept)\n \n // 生成或使用 groupId\n this.ensureGroupId()\n \n // 验证文件类型\n if (!this.validateFileType(newFile)) {\n this.removeInvalidFile(newFile)\n return\n }\n \n console.log('文件类型验证通过,继续上传')\n },\n \n /**\n * 处理文件更新\n */\n handleFileUpdate(newFile) {\n console.log('update', newFile)\n const { success, active, chunk, response } = newFile\n \n if (chunk && success && !active) {\n console.log('chunk end')\n this.handleChunkComplete(response)\n }\n },\n \n /**\n * 处理文件删除\n */\n handleFileDelete() {\n console.log('delete')\n },\n \n /**\n * 确保 groupId 存在\n */\n ensureGroupId() {\n console.log('onWatchInputFiles - multiple:', this.multiple, 'current groupId:', this.groupId)\n \n if (this.multiple && !this.groupId) {\n this.groupId = uuidv4()\n console.log('Generated new groupId:', this.groupId)\n } else if (this.multiple && this.groupId) {\n console.log('Using existing groupId:', this.groupId)\n }\n },\n \n /**\n * 移除无效文件\n */\n removeInvalidFile(file) {\n console.log('文件类型验证失败,尝试移除文件')\n console.log('uploadRef:', this.uploadRef)\n console.log('$refs:', this.$refs)\n \n if (this.$refs[this.uploadRef]) {\n this.$refs[this.uploadRef].remove(file)\n } else {\n console.error('无法找到 uploadRef 引用')\n }\n },\n \n /**\n * 处理分片上传完成\n */\n handleChunkComplete(response) {\n const { data: { file, type } } = response\n const payloads = {\n filePath: file.match(/\\/cw(.*)/) ? file.match(/\\/cw(.*)/)[0] : void 0,\n asyncID: uuidv4(),\n isDeleteOrigin: false,\n toImage: type === 'pdf',\n unzip: type === 'zip',\n _csrf: localStorage.getItem('token')\n }\n \n this.saveToServerAsyncPageTimer = setInterval(() => {\n this.saveToServerAsyncPage(payloads)\n }, CONSTANTS.SAVE_INTERVAL)\n },\n \n /**\n * 激活上传组件\n */\n activateUploadComponent(newFile, oldFile) {\n if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {\n if (!this.$refs[this.uploadRef].active) {\n this.$refs[this.uploadRef].active = true\n }\n }\n },\n \n // ==================== 值变化处理 ====================\n \n /**\n * 重置文件状态\n */\n resetFiles() {\n this.files = []\n this.buildedFiles = []\n this.groupId = null\n console.log('Reset groupId to null')\n },\n \n /**\n * 处理多文件模式的值变化\n */\n async handleMultipleFileValue(value) {\n // multiple - value 就是 groupId\n // 只有当 groupId 发生变化时才重新获取文件列表(初始化回显)\n if (this.groupId !== value) {\n this.groupId = value\n console.log('Set groupId from external value:', this.groupId)\n await this.fetchFilesWithGroupId()\n } else {\n console.log('GroupId unchanged, skip fetchFilesWithGroupId')\n }\n },\n \n /**\n * 处理单文件模式的值变化\n */\n async handleSingleFileValue() {\n await this.fetchFileWithFileId()\n }\n }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n::v-deep .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n &:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n }\n border-radius: var(--idooel-form-border-radius);\n}\n.ele-upload__wrapper {\n width: 100%;\n .ele-upload__area {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n .ele-upload__area--icon {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n .anticon-cloud-upload {\n font-size: 48px;\n color: var(--idooel-primary-color);\n }\n .anticon {\n font-size: 48px;\n color: var(--idooel-primary-color);\n }\n }\n .ele-upload__area--text {\n margin-left: 16px;\n .ele-upload__message {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n }\n .ele-upload__ext {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n }\n }\n }\n .ele-files__wrapper {\n .ele-file__item {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n .ele-file__suffix--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n .ele-file__name {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n .ele-file__inner {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n .ele-file__delete {\n margin-left: 8px;\n .ele-file__delete--icon {\n margin-left: 8px;\n cursor: pointer;\n }\n }\n }\n }\n}\n</style>","::v-deep .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n border-radius: var(--idooel-form-border-radius);\n}\n::v-deep .ele-upload__inner:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n}\n\n.ele-upload__wrapper {\n width: 100%;\n}\n.ele-upload__wrapper .ele-upload__area {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon-cloud-upload {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text {\n margin-left: 16px;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__message {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__ext {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__suffix--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name .ele-file__inner {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete {\n margin-left: 8px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete .ele-file__delete--icon {\n margin-left: 8px;\n cursor: pointer;\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
6606
|
+
inject("data-v-01d80c54_0", { source: "[data-v-01d80c54] .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n border-radius: var(--idooel-form-border-radius);\n}\n[data-v-01d80c54] .ele-upload__inner:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n}\n.ele-upload__wrapper[data-v-01d80c54] {\n width: 100%;\n}\n.ele-upload__wrapper .ele-upload__area[data-v-01d80c54] {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon[data-v-01d80c54] {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon-cloud-upload[data-v-01d80c54] {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon[data-v-01d80c54] {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text[data-v-01d80c54] {\n margin-left: 16px;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__message[data-v-01d80c54] {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__ext[data-v-01d80c54] {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item[data-v-01d80c54] {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__suffix--icon[data-v-01d80c54] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name[data-v-01d80c54] {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name .ele-file__inner[data-v-01d80c54] {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete[data-v-01d80c54] {\n margin-left: 8px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete .ele-file__delete--icon[data-v-01d80c54] {\n margin-left: 8px;\n cursor: pointer;\n}\n\n/*# sourceMappingURL=index.vue.map */", map: {"version":3,"sources":["/Users/huangshan/Goldgov/GanJiao/base-elearning-frontend-model/packages/components/packages/upload/src/index.vue","index.vue"],"names":[],"mappings":"AAkvBA;EACA,qBAAA;EACA,eAAA;EACA,wDAAA;EACA,yDAAA;EAIA,+CAAA;ACpvBA;ADivBA;EACA,0DAAA;AC/uBA;ADmvBA;EACA,WAAA;AChvBA;ADivBA;EACA,aAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,mBAAA;AC/uBA;ADgvBA;EACA,kCAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,cAAA;AC9uBA;AD+uBA;EACA,eAAA;EACA,kCAAA;AC7uBA;AD+uBA;EACA,eAAA;EACA,kCAAA;AC7uBA;ADgvBA;EACA,iBAAA;AC9uBA;AD+uBA;EACA,eAAA;EACA,8BAAA;EACA,gBAAA;AC7uBA;AD+uBA;EACA,gBAAA;EACA,eAAA;EACA,6BAAA;AC7uBA;ADkvBA;EACA,WAAA;EACA,eAAA;EACA,iBAAA;EACA,+CAAA;EACA,8CAAA;EACA,aAAA;EACA,mBAAA;EACA,mBAAA;AChvBA;ADivBA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;AC/uBA;ADivBA;EACA,OAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;AC/uBA;ADgvBA;EACA,gBAAA;EACA,uBAAA;AC9uBA;ADivBA;EACA,gBAAA;AC/uBA;ADgvBA;EACA,gBAAA;EACA,eAAA;AC9uBA;;AAEA,oCAAoC","file":"index.vue","sourcesContent":["<template>\n <div class=\"ele-upload__wrapper\">\n <FileUpload\n class=\"ele-upload__inner\"\n v-show=\"isShowUploadContainer\"\n v-model=\"files\"\n :ref=\"uploadRef\"\n :drop=\"drop\"\n :chunk-enabled=\"chunkEnabled\"\n :chunk=\"chunkConfig\"\n :accept=\"accept\"\n :size=\"fileSizeLimit\"\n :post-action=\"postAction\"\n :multiple=\"multiple\"\n :headers=\"headers\"\n :maximum=\"getMaximum\"\n :data=\"uploadParams\"\n @input-file=\"onWatchInputFiles\"\n @input=\"onWatchFiles\"\n style=\"width: 100%;\">\n <section class=\"ele-upload__area\">\n <div class=\"ele-upload__area--icon\">\n <template v-if=\"iconIsZhWrod\">\n {{ icon }}\n </template>\n <template v-else>\n <ele-icon :type=\"icon\"></ele-icon>\n </template>\n </div>\n <div class=\"ele-upload__area--text\">\n <div class=\"ele-upload__message\" v-if=\"message\" v-html=\"message\"></div>\n <div class=\"ele-upload__message\" v-else>单击或拖动文件到该区域以上传</div>\n <div class=\"ele-upload__ext\" v-if=\"ext\" v-html=\"ext\"></div>\n <div class=\"ele-upload__ext\" v-else>文件小于{{ size }}M</div>\n </div>\n </section>\n </FileUpload>\n <section class=\"ele-files__wrapper\">\n <!-- 显示正在上传的文件(有进度条) -->\n <div class=\"ele-file__item\" v-for=\"(file, idx) in uploadingFiles\" :key=\"`uploading-${idx}`\">\n <div class=\"ele-file__suffix--icon\">\n <ele-icon :type=\"fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'\"></ele-icon>\n </div>\n <div class=\"ele-file__name\">\n <div class=\"ele-file__inner\">{{ file.name }}</div>\n <div v-if=\"file.progress !== undefined\" class=\"ele-uplpad__progress\">\n <a-progress :strokeWidth=\"2\" :percent=\"Number(file.progress)\" size=\"small\" />\n </div>\n </div>\n <div class=\"ele-file__delete\" v-if=\"file.success || file.error\">\n <span class=\"ele-file__size\">{{ (file.size / byteConversion).toFixed(2) }}M</span>\n <span class=\"ele-file__delete--icon\" @click=\"handleClickDelete(file)\">\n <ele-icon type=\"delete\"></ele-icon>\n </span>\n </div>\n </div>\n \n <!-- 显示已上传完成的文件 -->\n <div class=\"ele-file__item\" v-for=\"(file, idx) in completedFiles\" :key=\"`completed-${idx}`\">\n <div class=\"ele-file__suffix--icon\">\n <ele-icon :type=\"fileSuffixIcon[file.suffix] ? fileSuffixIcon[file.suffix].name : 'icon-file'\"></ele-icon>\n </div>\n <div class=\"ele-file__name\">\n <div class=\"ele-file__inner\" @click=\"handleClickDownload(file)\">{{ file.name }}</div>\n </div>\n <div class=\"ele-file__delete\">\n <span class=\"ele-file__size\">{{ (file.size / byteConversion).toFixed(2) }}M</span>\n <span class=\"ele-file__delete--icon\" @click=\"handleClickDelete(file)\">\n <ele-icon type=\"delete\"></ele-icon>\n </span>\n </div>\n </div>\n </section>\n </div>\n</template>\n\n<script>\nimport FileUpload from 'vue-upload-component'\nimport { v4 as uuidv4 } from 'uuid'\nimport { route, net, type } from '@idooel/shared'\n\n// 常量定义\nconst CONSTANTS = {\n DEFAULT_URL: 'zuul/api-file/workbench/file',\n DEFAULT_ICON: '上传',\n DEFAULT_SIZE: 100,\n DEFAULT_MESSAGE: '单击或拖动文件到该区域以上传',\n DEFAULT_MAXIMUM: 20,\n BYTE_CONVERSION: 1024 * 1024,\n CHUNK_MIN_SIZE: 3 * 1024 * 1024,\n CHUNK_MAX_ACTIVE: 3,\n CHUNK_MAX_RETRIES: 5,\n SAVE_INTERVAL: 2000\n}\n\n// 文件后缀图标映射\nconst FILE_SUFFIX_ICONS = {\n 'doc': { name: 'icon-doc' },\n 'html': { name: 'icon-html' },\n 'mp4': { name: 'icon-mp' },\n 'pdf': { name: 'icon-pdf' },\n 'ppt': { name: 'icon-ppt' },\n 'psd': { name: 'icon-psd' },\n 'rtf': { name: 'icon-rtf' },\n 'txt': { name: 'icon-txt' },\n 'vis': { name: 'icon-vis' },\n 'xls': { name: 'icon-xls' },\n 'xml': { name: 'icon-xml' },\n 'zip': { name: 'icon-zip' },\n 'jpg': { name: 'icon-img' },\n 'mp3': { name: 'icon-mp1' }\n}\n\nexport default {\n name: 'ele-upload',\n components: {\n FileUpload\n },\n model: {\n prop: 'value',\n event: 'change'\n },\n props: {\n url: {\n type: String,\n default: CONSTANTS.DEFAULT_URL\n },\n icon: {\n type: String,\n default: CONSTANTS.DEFAULT_ICON\n },\n size: {\n type: Number,\n default: CONSTANTS.DEFAULT_SIZE\n },\n message: {\n type: String,\n default: CONSTANTS.DEFAULT_MESSAGE\n },\n ext: {\n type: String\n },\n extensions: {\n type: String\n },\n accept: {\n type: String\n },\n maximum: {\n type: Number,\n default: CONSTANTS.DEFAULT_MAXIMUM\n },\n multiple: {\n type: Boolean,\n default: false\n },\n drop: {\n type: Boolean,\n default: true\n },\n value: {\n type: [String, Array]\n },\n querys: {\n type: Object,\n default: () => ({\n _csrf: localStorage.getItem('token')\n })\n },\n headers: {\n type: Object,\n default: () => ({\n 'X-XSRF-TOKEN': localStorage.getItem('token')\n })\n },\n byteConversion: {\n type: Number,\n default: CONSTANTS.BYTE_CONVERSION\n },\n chunkEnabled: {\n type: Boolean,\n default: true\n }\n },\n data() {\n return {\n // 文件状态管理\n files: [], // vue-upload-component 管理的文件\n buildedFiles: [], // 已构建完成的文件列表\n \n // 上传状态管理\n saveToServerAsyncPageTimer: null,\n uploadRefId: null,\n // 多选且外部无值时,预先生成 groupId,确保首次上传参数完整\n groupId: (this.multiple && !this.value) ? uuidv4() : null\n }\n },\n created() {\n // 多文件模式下,如果没有外部 groupId,则生成一个\n if (this.multiple && !this.value) {\n this.groupId = uuidv4()\n console.log('Created with new groupId:', this.groupId)\n }\n },\n watch: {\n value: {\n async handler(value) {\n if (type.isEmpty(value)) {\n this.resetFiles()\n } else if (this.multiple) {\n this.handleMultipleFileValue(value)\n } else {\n this.handleSingleFileValue()\n }\n },\n immediate: true\n }\n },\n computed: {\n // ==================== 基础配置 ====================\n prefixPath() {\n return window.prefixPath\n },\n \n uploadRef() {\n if (!this.uploadRefId) {\n this.uploadRefId = `uploadRef_${uuidv4()}`\n }\n return this.uploadRefId\n },\n \n iconIsZhWord() {\n return type.isZhWord(this.icon)\n },\n \n // ==================== 上传配置 ====================\n uploadParams() {\n return this.multiple ? { groupID: this.groupId } : {}\n },\n \n fileSizeLimit() {\n return this.size * this.byteConversion\n },\n \n postAction() {\n const queryString = route.toQueryString(this.querys)\n return `${this.prefixPath}${this.url}?${queryString}`\n },\n \n chunkConfig() {\n return {\n action: `${this.prefixPath}zuul/api-file/workbench/file/temp/chunk/vue`,\n headers: { ...this.headers },\n minSize: CONSTANTS.CHUNK_MIN_SIZE,\n maxActive: CONSTANTS.CHUNK_MAX_ACTIVE,\n maxRetries: CONSTANTS.CHUNK_MAX_RETRIES,\n startBody: { override: true, path: '/cw' },\n uploadBody: { override: true, path: '/cw' },\n finishBody: { override: true, path: '/cw' }\n }\n },\n \n getMaximum() {\n return this.multiple ? this.maximum : 1\n },\n \n // ==================== 文件状态 ====================\n uploadingFiles() {\n return this.files.filter(file => file.progress !== undefined && !file.success)\n },\n \n completedFiles() {\n return this.buildedFiles.filter(file => file.fileID)\n },\n \n totalFiles() {\n return this.uploadingFiles.length + this.completedFiles.length\n },\n \n isFileUploadSuccessed() {\n const currentUploadingFiles = this.files.filter(file => file.response !== undefined)\n if (currentUploadingFiles.length === 0) {\n return this.buildedFiles.length > 0\n }\n return currentUploadingFiles.every(file => file.success)\n },\n \n isShowUploadContainer() {\n const maxFiles = this.multiple ? this.maximum : 1\n return this.totalFiles < maxFiles\n },\n \n // ==================== 文件信息 ====================\n fileSuffixIcon() {\n return FILE_SUFFIX_ICONS\n },\n \n fileIds() {\n if (this.multiple) {\n return this.groupId\n } else {\n const fileIds = this.buildedFiles.map(file => file.fileID)\n return fileIds[0]\n }\n },\n \n fileResponseData() {\n return this.multiple ? this.buildedFiles : this.buildedFiles[0]\n }\n },\n methods: {\n // ==================== 文件管理 ====================\n \n /**\n * 从多个数组中移除文件\n */\n removeFromArrays(file, arrays, key = 'fileID') {\n return arrays.map(arr => \n arr.filter(item => item[key] !== file[key] && item.id !== file.id)\n )\n },\n \n /**\n * 检查文件是否为新文件\n */\n isNewFile(newFile, existingFiles, key = 'fileID') {\n return newFile[key] && !existingFiles.some(existing => existing[key] === newFile[key])\n },\n \n /**\n * 合并文件数据\n */\n mergeFileData(uploadFile) {\n return {\n ...uploadFile.response.data,\n ...uploadFile\n }\n },\n \n /**\n * 初始化文件列表\n */\n async initializeFiles() {\n if (!this.value) return\n \n if (this.multiple) {\n await this.fetchFilesWithGroupId()\n } else {\n await this.fetchFileWithFileId()\n }\n },\n \n /**\n * 获取多文件组\n */\n async fetchFilesWithGroupId() {\n try {\n const response = await net.get(`/api-file/workbench/file/group/${this.value}`)\n const data = response.data || []\n \n // 只有在没有现有文件时才设置初始文件列表\n if (this.buildedFiles.length === 0) {\n this.buildedFiles = data\n console.log('Initial files loaded:', this.buildedFiles.length)\n } else {\n console.log('Keep existing files, skip initial load')\n }\n } catch (error) {\n console.log('fetchFilesWithGroupId error:', error)\n console.log('Keep current files, do not clear due to API error')\n }\n },\n \n /**\n * 获取单文件\n */\n async fetchFileWithFileId() {\n try {\n const response = await net.get(`/api-file/file/${this.value}`)\n const data = response.data\n this.buildedFiles = [data]\n this.files = [data]\n } catch (error) {\n console.log('fetchFileWithFileId error:', error)\n }\n },\n \n /**\n * 处理文件删除\n */\n handleClickDelete(file) {\n const { fileID } = file\n console.log('Deleting file:', { name: file.name, fileID })\n \n // 从上传组件中移除文件\n if (this.$refs[this.uploadRef]) {\n this.$refs[this.uploadRef].remove(file)\n }\n \n // 从所有数组中移除文件\n [this.files, this.buildedFiles] = this.removeFromArrays(file, [this.files, this.buildedFiles])\n \n console.log('After deletion - files:', this.files.length, 'buildedFiles:', this.buildedFiles.length)\n \n // 多文件模式下,如果删除最后一个文件,重置 groupId\n if (this.multiple && this.buildedFiles.length === 0) {\n this.groupId = null\n console.log('Reset groupId after deleting last file')\n }\n \n // 触发 change 事件\n this.$emit('change', this.fileIds)\n },\n \n /**\n * 处理文件下载\n */\n handleClickDownload(file) {\n const { fileID: fileId } = file\n window.open(`/api-file/workbench/file/stream/${fileId}?origin=true`)\n },\n // ==================== 上传处理 ====================\n \n /**\n * 处理文件上传状态变化\n */\n onWatchFiles(files) {\n console.log('onWatchFiles called with files:', files.length)\n console.log('Current buildedFiles:', this.buildedFiles.length)\n \n // 更新文件状态\n this.files = files\n \n // 处理已上传成功的文件\n this.processUploadedFiles(files)\n \n // 检查上传是否完成\n if (this.isFileUploadSuccessed) {\n this.$emit('change', this.fileIds)\n this.$emit('on-success', this.fileResponseData)\n }\n },\n \n /**\n * 处理已上传成功的文件\n */\n processUploadedFiles(files) {\n // 处理所有有响应的文件(包括正在上传和已完成的)\n const uploadedFiles = files.filter(file => file.response)\n const newBuildedFiles = uploadedFiles.map(file => this.mergeFileData(file))\n \n if (this.multiple) {\n this.processMultipleFiles(newBuildedFiles)\n } else {\n // 单文件模式:只保留最新的文件\n this.buildedFiles = newBuildedFiles\n }\n \n this.logFileStatus()\n },\n \n /**\n * 处理多文件模式\n */\n processMultipleFiles(newBuildedFiles) {\n // 获取已存在的文件ID集合\n const existingFileIds = new Set(this.buildedFiles.map(f => f.fileID).filter(id => id))\n \n // 过滤出真正的新文件\n const trulyNewFiles = newBuildedFiles.filter(newFile => \n this.isNewFile(newFile, this.buildedFiles)\n )\n \n console.log('Existing fileIDs:', Array.from(existingFileIds))\n console.log('New uploaded files:', newBuildedFiles.map(f => ({ name: f.name, fileID: f.fileID })))\n console.log('Truly new files:', trulyNewFiles.map(f => ({ name: f.name, fileID: f.fileID })))\n \n // 将新文件追加到现有文件列表\n if (trulyNewFiles.length > 0) {\n this.buildedFiles = [...this.buildedFiles, ...trulyNewFiles]\n console.log('Added new files, total buildedFiles:', this.buildedFiles.length)\n }\n \n // 更新现有文件的状态(包括新添加的文件)\n this.updateExistingFiles(newBuildedFiles)\n },\n \n /**\n * 更新现有文件状态\n */\n updateExistingFiles(newBuildedFiles) {\n this.buildedFiles = this.buildedFiles.map(existingFile => {\n const updatedFile = newBuildedFiles.find(newFile => newFile.fileID === existingFile.fileID)\n return updatedFile ? { ...existingFile, ...updatedFile } : existingFile\n })\n },\n \n /**\n * 记录文件状态日志\n */\n logFileStatus() {\n console.log('Final buildedFiles:', this.buildedFiles.length)\n console.log('buildedFiles details:', this.buildedFiles.map(f => ({ name: f.name, fileID: f.fileID, success: f.success })))\n console.log('Uploading files:', this.uploadingFiles.length)\n console.log('Completed files:', this.completedFiles.length)\n },\n // ==================== 异步处理 ====================\n \n /**\n * 异步保存文件到服务器\n */\n async saveToServerAsyncPage(payloads = {}) {\n try {\n const response = await net.post('zuul/api-file/workbench/file/temp/saveToServerAsyncPage', payloads, { \n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'\n }\n })\n \n const { data } = response\n if (data !== 'saveToServerAsyncPage') {\n clearInterval(this.saveToServerAsyncPageTimer)\n }\n } catch (error) {\n console.error('saveToServerAsyncPage error:', error)\n clearInterval(this.saveToServerAsyncPageTimer)\n }\n },\n \n // ==================== 文件验证 ====================\n \n /**\n * 验证文件类型\n */\n validateFileType(file) {\n if (!file || !file.name) {\n console.log('文件或文件名不存在')\n return false\n }\n \n const fileExt = this.getFileExtension(file.name)\n console.log('文件扩展名:', fileExt)\n \n if (this.extensions) {\n const allowedExts = this.getAllowedExtensions()\n console.log('允许的扩展名:', allowedExts)\n \n if (!allowedExts.includes(fileExt)) {\n console.log('扩展名不在允许列表中')\n this.$message.error(`不支持的文件类型 \"${fileExt}\",请上传 ${this.extensions} 格式的文件`)\n return false\n }\n }\n \n console.log('文件类型验证通过')\n return true\n },\n \n /**\n * 获取文件扩展名\n */\n getFileExtension(fileName) {\n return fileName.toLowerCase().substring(fileName.lastIndexOf('.'))\n },\n \n /**\n * 获取允许的扩展名列表\n */\n getAllowedExtensions() {\n return this.extensions.toLowerCase()\n .split(',')\n .map(ext => ext.trim().startsWith('.') ? ext.trim() : `.${ext.trim()}`)\n },\n // ==================== 事件处理 ====================\n \n /**\n * 处理文件输入事件\n */\n onWatchInputFiles(newFile, oldFile) {\n if (newFile && !oldFile) {\n this.handleFileAdd(newFile)\n } else if (newFile && oldFile) {\n this.handleFileUpdate(newFile)\n } else if (!newFile && oldFile) {\n this.handleFileDelete()\n }\n \n // 激活上传组件\n this.activateUploadComponent(newFile, oldFile)\n },\n \n /**\n * 生成唯一的随机数\n */\n generateUniqueRandom() {\n // 使用 crypto.getRandomValues 生成更安全的随机数\n if (window.crypto && window.crypto.getRandomValues) {\n const array = new Uint32Array(1)\n window.crypto.getRandomValues(array)\n return array[0].toString()\n }\n // 降级方案:使用 Math.random 结合时间戳\n return Math.random().toString(36).substr(2, 9) + Date.now().toString(36)\n },\n\n /**\n * 处理文件添加\n */\n handleFileAdd(newFile) {\n // 为每个文件生成唯一的随机数\n const uniqueRandom = this.generateUniqueRandom()\n newFile.postAction = newFile.postAction + '&_t=' + uniqueRandom\n console.log('add file:', newFile)\n console.log('extensions:', this.extensions)\n console.log('accept:', this.accept)\n \n // 生成或使用 groupId\n this.ensureGroupId()\n \n // 验证文件类型\n if (!this.validateFileType(newFile)) {\n this.removeInvalidFile(newFile)\n return\n }\n console.log('文件类型验证通过,继续上传')\n },\n \n /**\n * 处理文件更新\n */\n handleFileUpdate(newFile) {\n console.log('update', newFile)\n const { success, active, chunk, response } = newFile\n \n if (chunk && success && !active) {\n console.log('chunk end')\n this.handleChunkComplete(response)\n }\n },\n \n /**\n * 处理文件删除\n */\n handleFileDelete() {\n console.log('delete')\n },\n \n /**\n * 确保 groupId 存在\n */\n ensureGroupId() {\n console.log('onWatchInputFiles - multiple:', this.multiple, 'current groupId:', this.groupId)\n \n if (this.multiple && !this.groupId) {\n this.groupId = uuidv4()\n console.log('Generated new groupId:', this.groupId)\n } else if (this.multiple && this.groupId) {\n console.log('Using existing groupId:', this.groupId)\n }\n },\n \n /**\n * 移除无效文件\n */\n removeInvalidFile(file) {\n console.log('文件类型验证失败,尝试移除文件')\n console.log('uploadRef:', this.uploadRef)\n console.log('$refs:', this.$refs)\n \n if (this.$refs[this.uploadRef]) {\n this.$refs[this.uploadRef].remove(file)\n } else {\n console.error('无法找到 uploadRef 引用')\n }\n },\n \n /**\n * 处理分片上传完成\n */\n handleChunkComplete(response) {\n const { data: { file, type } } = response\n const payloads = {\n filePath: file.match(/\\/cw(.*)/) ? file.match(/\\/cw(.*)/)[0] : void 0,\n asyncID: uuidv4(),\n isDeleteOrigin: false,\n toImage: type === 'pdf',\n unzip: type === 'zip',\n _csrf: localStorage.getItem('token')\n }\n \n this.saveToServerAsyncPageTimer = setInterval(() => {\n this.saveToServerAsyncPage(payloads)\n }, CONSTANTS.SAVE_INTERVAL)\n },\n \n /**\n * 激活上传组件\n */\n activateUploadComponent(newFile, oldFile) {\n if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {\n if (!this.$refs[this.uploadRef].active) {\n this.$refs[this.uploadRef].active = true\n }\n }\n },\n \n // ==================== 值变化处理 ====================\n \n /**\n * 重置文件状态\n */\n resetFiles() {\n this.files = []\n this.buildedFiles = []\n // 多选模式下保留或生成 groupId,避免首次上传为空\n if (this.multiple) {\n if (!this.groupId) {\n this.groupId = uuidv4()\n console.log('Generated groupId in resetFiles:', this.groupId)\n } else {\n console.log('Preserve existing groupId in resetFiles:', this.groupId)\n }\n } else {\n this.groupId = null\n console.log('Reset groupId to null (single mode)')\n }\n },\n \n /**\n * 处理多文件模式的值变化\n */\n async handleMultipleFileValue(value) {\n // multiple - value 就是 groupId\n // 只有当 groupId 发生变化时才重新获取文件列表(初始化回显)\n if (this.groupId !== value) {\n this.groupId = value\n console.log('Set groupId from external value:', this.groupId)\n await this.fetchFilesWithGroupId()\n } else {\n console.log('GroupId unchanged, skip fetchFilesWithGroupId')\n }\n },\n \n /**\n * 处理单文件模式的值变化\n */\n async handleSingleFileValue() {\n await this.fetchFileWithFileId()\n }\n }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n::v-deep .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n &:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n }\n border-radius: var(--idooel-form-border-radius);\n}\n.ele-upload__wrapper {\n width: 100%;\n .ele-upload__area {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n .ele-upload__area--icon {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n .anticon-cloud-upload {\n font-size: 48px;\n color: var(--idooel-primary-color);\n }\n .anticon {\n font-size: 48px;\n color: var(--idooel-primary-color);\n }\n }\n .ele-upload__area--text {\n margin-left: 16px;\n .ele-upload__message {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n }\n .ele-upload__ext {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n }\n }\n }\n .ele-files__wrapper {\n .ele-file__item {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n .ele-file__suffix--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n .ele-file__name {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n .ele-file__inner {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n .ele-file__delete {\n margin-left: 8px;\n .ele-file__delete--icon {\n margin-left: 8px;\n cursor: pointer;\n }\n }\n }\n }\n}\n</style>","::v-deep .ele-upload__inner {\n opacity: 1 !important;\n cursor: pointer;\n border: 1px dashed var(--idooel-form-title-border-color);\n background: var(--idooel-form-upload-bg-color) !important;\n border-radius: var(--idooel-form-border-radius);\n}\n::v-deep .ele-upload__inner:hover {\n border-color: var(--idooel-form-upload-border-hover-color);\n}\n\n.ele-upload__wrapper {\n width: 100%;\n}\n.ele-upload__wrapper .ele-upload__area {\n padding: 16px;\n width: 100%;\n height: 80px;\n display: flex;\n flex-direction: row;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon {\n color: var(--idooel-primary-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n font-size: 16x;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon-cloud-upload {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--icon .anticon {\n font-size: 48px;\n color: var(--idooel-primary-color);\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text {\n margin-left: 16px;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__message {\n font-size: 16px;\n color: var(--idoole-black-088);\n text-align: left;\n}\n.ele-upload__wrapper .ele-upload__area .ele-upload__area--text .ele-upload__ext {\n text-align: left;\n font-size: 14px;\n color: var(--idoole-black-06);\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item {\n width: 100%;\n margin-top: 8px;\n padding: 8px 12px;\n border-radius: var(--idooel-form-border-radius);\n background: var(--idooel-form-upload-bg-color);\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__suffix--icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name {\n flex: 1;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n font-size: 14px;\n margin-left: 8px;\n cursor: pointer;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__name .ele-file__inner {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete {\n margin-left: 8px;\n}\n.ele-upload__wrapper .ele-files__wrapper .ele-file__item .ele-file__delete .ele-file__delete--icon {\n margin-left: 8px;\n cursor: pointer;\n}\n\n/*# sourceMappingURL=index.vue.map */"]}, media: undefined });
|
|
6144
6607
|
|
|
6145
6608
|
};
|
|
6146
6609
|
/* scoped */
|
|
6147
|
-
const __vue_scope_id__$t = "data-v-
|
|
6610
|
+
const __vue_scope_id__$t = "data-v-01d80c54";
|
|
6148
6611
|
/* module identifier */
|
|
6149
6612
|
const __vue_module_identifier__$t = undefined;
|
|
6150
6613
|
/* functional template */
|