1/** 2 * æ¡ä»¶æå»ºå¨æ¨¡å 3 * åè½ï¼æä¾å¯è§åæ¡ä»¶ç¼è¾çé¢ï¼æ¯ææå¨è¾å ¥åæå»ºå¨ä¸¤ç§æ¨¡å¼ 4 * æ ¸å¿æ¹æ³ï¼builderRender() - 渲ææå»ºå¨çé¢, parseManualCondition() - è§£ææå¨æ¡ä»¶ 5 * ä¾èµï¼ui-utils.js æä¾çå¡åè·ååè½ 6 */ 7// æ¡ä»¶æå»ºå¨æ¨¡å 8(function (global) { 9 // ====== æ°æ®ç»æä¸å·¥å · ====== 10 // æå»ºå¨çæ ¹æ¡ä»¶å¯¹è±¡ 11 let builderRootCondition = null; 12 // æå»ºå¨çæçæ¡ä»¶è¡¨è¾¾å¼ææ¬ 13 let builderConditionText = ''; 14 // å½åè¾å ¥æ¨¡å¼ï¼manual/builderï¼ 15 let currentConditionInputMode = 'manual'; 16 17 // è¿ç®ç¬¦æ å°è¡¨ 18 const builderOperators = { 19 gt: '>', 20 eq: '==', 21 lt: '<', 22 neq: '!=', 23 gte: '>=', 24 lte: '<=' 25 }; 26 27 // è·åææå¡åï¼åéååèªå®ä¹åï¼ 28 function getAllCardNames() { 29 const varNames = []; 30 for (let i = 0; i < 52; i++) { 31 const count = document.getElementById(`card${i}`)?.value; 32 const name = document.getElementById(`cardName${i}`)?.value.trim(); 33 if ((count && parseInt(count) > 0) || (name && name !== '')) { 34 if (i < 26) { 35 varNames.push(String.fromCharCode(97 + i)); 36 } else { 37 varNames.push('a' + String.fromCharCode(97 + i - 26)); 38 } 39 } 40 } 41 const customNames = []; 42 for (let i = 0; i < 52; i++) { 43 const name = document.getElementById(`cardName${i}`)?.value.trim(); 44 if (name && !varNames.includes(name) && !customNames.includes(name)) customNames.push(name); 45 } 46 const allNames = [...customNames, ...varNames]; 47 return allNames.length > 0 ? allNames : ['请å è¾å ¥æ°éæå¡å']; 48 } 49 50 // å建æ¡ä»¶èç¹å¯¹è±¡ï¼åæ¡ä»¶ææ¡ä»¶ç»ï¼ 51 function builderCreateCondition(type, children) { 52 return type === 'single' ? { 53 type: 'single', 54 cards: [{ name: getAllCardNames()[0] }], 55 symbol: 'gt', 56 num: '0' 57 } : { type, children: children || [] }; 58 } 59 60 // å建æ¦ç彿°èç¹ 61 function builderCreateProbCondition(value = "50") { 62 return { 63 type: 'prob', 64 value: String(value) // ä¿æåå§åç¬¦ä¸²å¼ 65 }; 66 } 67 68 // ====== 渲æé»è¾ ====== 69 // æ¸²ææ´ä¸ªæ¡ä»¶æå»ºå¨çé¢ 70 function builderRender() { 71 const builder = document.getElementById('conditionBuilder'); 72 if (!builder) return; 73 builder.innerHTML = ''; 74 builderRootCondition && builder.appendChild(builderRenderCondition(builderRootCondition, true)); 75 builderUpdateOutput(); 76 } 77 // 渲æå个æ¡ä»¶èç¹ï¼éå½ï¼ 78 function builderRenderCondition(condition, isRoot = false) { 79 const container = document.createElement('div'); 80 // æ¦ç彿°ä¹ç»ä¸ç¨condition-singleæ ·å¼ 81 if (condition.type === 'prob') { 82 container.className = 'condition-builder condition-single'; 83 builderRenderProbCondition(condition, container, isRoot); 84 } else { 85 container.className = `condition-builder condition-${condition.type}`; 86 if (condition.type === 'single') { 87 builderRenderSingleCondition(condition, container, isRoot); 88 } else { 89 builderRenderGroupCondition(condition, container, isRoot); 90 } 91 } 92 return container; 93 } 94 // 渲æåæ¡ä»¶èç¹ï¼å¦âæ½å°Xçæ°é > Nâï¼ 95 function builderRenderSingleCondition(condition, container, isRoot) { 96 container.appendChild(document.createTextNode('æ½å°')); 97 const cardsWrapper = document.createElement('div'); 98 cardsWrapper.className = 'builder-cards-wrapper'; 99 condition.cards.forEach((card, index) => { 100 const cardRow = document.createElement('div'); 101 cardRow.className = 'builder-card-row'; 102 if (index > 0) { 103 const opSelect = builderCreateSelect(['+', '-', '*', '/'], card.operator || '+', 104 e => { card.operator = e.target.value; builderUpdateOutput(); }); 105 cardRow.appendChild(opSelect); 106 } 107 const cardNameContainer = document.createElement('span'); 108 let cardNameControl = builderCreateSelect( 109 getAllCardNames().map(name => ({ display: name, value: name })), 110 card.name, 111 e => { card.name = e.target.value; builderUpdateOutput(); } 112 ); 113 cardNameContainer.appendChild(cardNameControl); 114 const toggleButton = builderCreateButton('âï¸', () => { 115 if (cardNameControl.tagName.toLowerCase() === 'select') { 116 const customInput = builderCreateInput(card.name, e => { card.name = e.target.value; builderUpdateOutput(); }); 117 customInput.classList.add('builder-card-input'); 118 cardNameContainer.replaceChild(customInput, cardNameControl); 119 cardNameControl = customInput; 120 toggleButton.textContent = 'ð'; 121 } else { 122 const newSelect = builderCreateSelect( 123 getAllCardNames().map(name => ({ display: name, value: name })), 124 card.name, 125 e => { card.name = e.target.value; builderUpdateOutput(); } 126 ); 127 cardNameContainer.replaceChild(newSelect, cardNameControl); 128 cardNameControl = newSelect; 129 toggleButton.textContent = 'âï¸'; 130 } 131 }); 132 cardRow.appendChild(cardNameContainer); 133 cardRow.appendChild(toggleButton); 134 if (condition.cards.length > 1) { 135 cardRow.appendChild(builderCreateButton('Ã', () => { 136 condition.cards.splice(index, 1); 137 builderRender(); 138 })); 139 } 140 cardsWrapper.appendChild(cardRow); 141 }); 142 container.appendChild(cardsWrapper); 143 container.appendChild(builderCreateButton('+', () => { 144 condition.cards.push({ name: getAllCardNames()[0], operator: '+' }); 145 builderRender(); 146 })); 147 container.appendChild(document.createTextNode('çæ°é')); 148 const symbolSelect = builderCreateSelect( 149 Object.entries(builderOperators).map(([value, display]) => ({ display, value })), 150 condition.symbol, 151 e => { condition.symbol = e.target.value; builderUpdateOutput(); } 152 ); 153 container.appendChild(symbolSelect); 154 container.appendChild(builderCreateInput(condition.num, e => { condition.num = e.target.value; builderUpdateOutput(); }, '40px')); 155 !isRoot && container.appendChild(builderCreateButton('å é¤', () => {
156 container.dispatchEvent(new CustomEvent('delete', { bubbles: true })); 157 })); 158 } 159 // æ¸²ææ¦ç彿°èç¹ 160 function builderRenderProbCondition(condition, container, isRoot) { 161 container.appendChild(document.createTextNode('æ ')); 162 const valueInput = document.createElement('input'); 163 valueInput.type = 'number'; 164 valueInput.value = condition.value; 165 valueInput.min = 0; 166 valueInput.max = 100; 167 valueInput.step = 0.01; 168 169 // åªå¨å¤±å»ç¦ç¹æ¶è¿è¡å®æ´éªè¯ 170 valueInput.addEventListener('blur', e => { 171 let value = parseFloat(e.target.value); 172 if (value > 100) value = 100; 173 condition.value = value; 174 e.target.value = value; 175 builderUpdateOutput(); 176 }); 177 178 // è¾å ¥æ¶åªååºæ¬éªè¯ï¼ä¸æ´æ°conditionå¼ 179 valueInput.addEventListener('input', e => { 180 if (e.target.value === '' || e.target.value === '.') return; 181 let value = parseFloat(e.target.value); 182 if (isNaN(value)) { 183 e.target.value = condition.value; 184 } 185 }); 186 container.appendChild(valueInput); 187 container.appendChild(document.createTextNode('% çæ¦çæ»¡è¶³æ¤æ¡ä»¶')); 188 if (!isRoot) { 189 container.appendChild(builderCreateButton('å é¤', () => { 190 container.dispatchEvent(new CustomEvent('delete', { bubbles: true })); 191 })); 192 } 193 } 194 // æ¸²ææ¡ä»¶ç»èç¹ï¼å¦âå ¨é¨æ»¡è¶³/任䏿»¡è¶³âï¼ 195 function builderRenderGroupCondition(condition, container, isRoot) { 196 const header = document.createElement('div'); 197 header.className = 'builder-group-header'; 198 header.appendChild(document.createTextNode('满足以ä¸')); 199 header.appendChild(builderCreateSelect( 200 ['å ¨é¨', 'ä»»ä¸'].map((text, i) => ({ display: text, value: i === 0 ? 'and' : 'or' })), 201 condition.type, 202 e => { condition.type = e.target.value; builderUpdateOutput(); } 203 )); 204 header.appendChild(document.createTextNode('æ¡ä»¶')); 205 if (!isRoot) header.appendChild(builderCreateButton('å é¤', () => { 206 container.dispatchEvent(new CustomEvent('delete', { bubbles: true })); 207 })); 208 container.appendChild(header); 209 const childrenContainer = document.createElement('div'); 210 childrenContainer.className = 'builder-group-children'; 211 condition.children.forEach(child => { 212 const childElement = builderRenderCondition(child); 213 childElement.addEventListener('delete', e => { 214 e.stopPropagation(); 215 const index = condition.children.indexOf(child); 216 index !== -1 && condition.children.splice(index, 1) && builderRender(); 217 }); 218 childrenContainer.appendChild(childElement); 219 }); 220 const buttons = document.createElement('div'); 221 buttons.className = 'builder-buttons'; 222 buttons.appendChild(builderCreateButton('æ·»å æ¡ä»¶ç»', () => { 223 condition.children.push(builderCreateCondition('and', [])); 224 builderRender(); 225 })); 226 buttons.appendChild(builderCreateButton('æ·»å æ¡ä»¶',
226() => { 227 condition.children.push(builderCreateCondition('single')); 228 builderRender(); 229 })); 230 const probBtn = builderCreateButton('æ·»å æ¦ç彿°', () => { 231 condition.children.push(builderCreateProbCondition()); 232 builderRender(); 233 }); 234 probBtn.className += ' btn--auto-width'; 235 buttons.appendChild(probBtn); 236 childrenContainer.appendChild(buttons); 237 container.appendChild(childrenContainer); 238 } 239 // æ´æ°æ¡ä»¶è¡¨è¾¾å¼ææ¬æ¡åé¢è§ 240 function builderUpdateOutput() { 241 builderConditionText = builderRootCondition ? builderGenerateConditionText(builderRootCondition) : ''; 242 if (document.querySelector('input[name="conditionInputMode"]:checked')?.value === 'builder') { 243 document.getElementById('condition').value = builderConditionText; 244 const preview = document.getElementById('builderConditionPreview'); 245 if (preview) preview.value = builderConditionText; 246 } 247 } 248 // çææ¡ä»¶è¡¨è¾¾å¼åç¬¦ä¸²ï¼æ¯æprobç±»å 249 function builderGenerateConditionText(condition) { 250 if (condition.type === 'single') { 251 const cardsText = condition.cards.map((c, i) => 252 i === 0 ? c.name : `${c.operator || '+'} ${c.name}`).join(' '); 253 const operator = builderOperators[condition.symbol] || condition.symbol || ''; 254 return `(${cardsText}) ${operator} ${condition.num}`; 255 } else if (condition.type === 'prob') { 256 return `PROB(${condition.value})`; // ç´æ¥è¾åºåå§å¼ 257 } 258 const childrenText = condition.children.map(builderGenerateConditionText).filter(Boolean); 259 return childrenText.length > 1 260 ? `(${childrenText.join(condition.type === 'and' ? ' && ' : ' || ')})` 261 : childrenText[0] || ''; 262 } 263 // åå»ºä¸ææ¡æ§ä»¶ 264 function builderCreateSelect(options, value, onChange) { 265 const select = document.createElement('select'); 266 options.forEach(opt => { 267 const option = document.createElement('option'); 268 option.value = opt.value || opt; 269 option.textContent = opt.display || opt; 270 select.appendChild(option); 271 }); 272 select.value = value; 273 select.addEventListener('change', onChange); 274 return select; 275 } 276 // å建è¾å ¥æ¡æ§ä»¶ 277 function builderCreateInput(value, onChange, width) { 278 const input = document.createElement('input'); 279 input.type = 'text'; 280 input.value = value; 281 input.setAttribute('list', 'cardNamesDatalist'); 282 if (width) input.style.width = width; 283 input.addEventListener('input', onChange); 284 return input; 285 } 286 // å建æé®æ§ä»¶ 287 function builderCreateButton(text, onClick) { 288 const button = document.createElement('button'); 289 button.type = 'button'; 290 button.textContent = text; 291 let variant = 'btn--outline'; 292 if (text.trim() === 'Ã' || text.trim() === 'å é¤') { 293 variant = 'btn--danger'; 294 } 295 button.className = `btn ${variant}`; 296 if (text.trim() === 'æ·»å æ¡ä»¶ç»' || text.trim() === 'æ·»å æ¡ä»¶') { 297 button.className += ' btn--auto-width'; 298 } 299 button.addEventListener('click', onClick); 300 return button; 301 } 302 303 // ====== æå¨æ¡ä»¶è§£æå¨ ====== 304 // å°è¡¨è¾¾å¼å符串åå²ä¸ºè¯æ³åå 305 function tokenize(expr) { 306 const regex = /\s*([A-Za-z0-9\u4e00-\u9fa5]+|>=|<=|==|!=|&&|\|\||[-+*/()<>])\s*/g; 307 let tokens = []; 308 let m; 309 while ((m = regex.exec(expr)) !== null) { 310 tokens.push(m[1]); 311 } 312 return tokens; 313 } 314 // ç®å表达å¼è§£æå¨æé 彿° 315 function Parser(tokens) { 316 this.tokens = tokens; 317 this.pos = 0; 318 } 319 // æ¥çä¸ä¸ä¸ªtoken 320 Parser.prototype.peek = function () { return this.tokens[this.pos]; }; 321 // æ¶èä¸ä¸ªtoken 322 Parser.prototype.consume = function (token) { 323 if (token && this.tokens[this.pos] !== token) { 324 throw new Error("颿 " + token + "ï¼ä½å¾å° " + this.tokens[this.pos]); 325 } 326 return this.tokens[this.pos++]; 327 }; 328 // 夿æ¯å¦å°è¾¾æ«å°¾ 329 Parser.prototype.eof = function () { return this.pos >= this.tokens.length; }; 330 // è§£æè¡¨è¾¾å¼å ¥å£ 331 function parseExpression(parser) { 332 return parseLogicalOr(parser); 333 } 334 // è§£ææè¡¨è¾¾å¼ 335 function parseLogicalOr(parser) { 336 let node = parseLogicalAnd(parser); 337 while (!parser.eof() && parser.peek() === '||') { 338 parser.consume('||'); 339 const right = parseLogicalAnd(parser); 340 node = { type: "or", children: [node, right] }; 341 } 342 return node; 343 } 344 // è§£æä¸è¡¨è¾¾å¼ 345 function parseLogicalAnd(parser) { 346 let node = parseRelational(parser); 347 while (!parser.eof() && parser.peek() === '&&') { 348 parser.consume('&&'); 349 const right = parseRelational(parser); 350 node = { type: "and", children: [node, right] }; 351 } 352 return node; 353 } 354 // è§£æå ³ç³»è¡¨è¾¾å¼ 355 function parseRelational(parser) { 356 let left = parseSum(parser); 357 if (!parser.eof() && /^(>
357=|<=|==|!=|>|<)$/.test(parser.peek())) { 358 const op = parser.consume(); 359 const num = parser.consume(); 360 if (!/^\d+$/.test(num)) { 361 throw new Error("颿æ°åï¼ä½å¾å° " + num); 362 } 363 let cards = []; 364 if (Array.isArray(left)) { 365 cards.push({ name: left[0] }); 366 for (let i = 1; i < left.length; i += 2) { 367 let operator = left[i]; 368 let operand = left[i + 1]; 369 cards.push({ operator, name: operand }); 370 } 371 } else { 372 cards.push({ name: left }); 373 } 374 return { type: "single", cards: cards, symbol: mapOperator(op), num: num }; 375 } 376 return left; 377 } 378 // è§£æå åè¡¨è¾¾å¼ 379 function parseSum(parser) { 380 if (parser.peek() === '(') { 381 parser.consume('('); 382 const node = parseExpression(parser); 383 parser.consume(')'); 384 return node; 385 } 386 // æ¯æPROB彿°å表达å¼éå½è§£æ 387 if (parser.peek() === 'PROB') { 388 parser.consume('PROB'); 389 parser.consume('('); 390 const value = parser.consume(); 391 if (!/^\d+(\.\d+)?$/.test(value)) { 392 throw new Error("PROB彿°åæ°å¿ é¡»æ¯æ°å"); 393 } 394 parser.consume(')'); 395 return { type: "prob", value: value }; // ä¿æåå§åç¬¦ä¸²å¼ 396 } 397 let items = []; 398 items.push(parser.consume()); 399 while (!parser.eof() && (parser.peek() === '+' || parser.peek() === '-')) { 400 let operator = parser.consume(); 401 items.push(operator); 402 items.push(parser.consume()); 403 } 404 return items.length === 1 ? items[0] : items; 405 } 406 // è¿ç®ç¬¦æ å°ï¼å符串转å 鍿 è¯ï¼ 407 function mapOperator(op) { 408 const opMap = { 409 ">": "gt", 410 "<": "lt", 411 "==": "eq", 412 "!=": "neq", 413 ">=": "gte", 414 "<=": "lte", 415 "大äºçäº": "gte", 416 "å°äºçäº": "lte", 417 "大äº": "gt", 418 "å°äº": "lt" 419 }; 420 if (!opMap[op]) throw new Error("䏿¯æçè¿ç®ç¬¦ï¼" + op); 421 return opMap[op]; 422 } 423 // è§£ææå¨è¾å ¥æ¡ä»¶ä¸ºæå»ºå¨ç»æ 424 function parseManualCondition(manualStr) { 425 manualStr = manualStr.trim(); 426 if (!manualStr) throw new Error("ç©ºçæ¡ä»¶"); 427 const tokens = tokenize(manualStr); 428 const parser = new Parser(tokens); 429 const tree = parseExpression(parser); 430 if (!parser.eof()) { 431 throw new Error("æ æ³è§£ææ¡ä»¶ï¼" + manualStr); 432 } 433 // ä¿®æ¹ï¼æ 论ä½ç§ç±»åé½å 裹å¨andæ¡ä»¶ç»ä¸ï¼ä¿è¯ææ¯èç¹ 434 if (tree && tree.type === 'and') { 435 return tree; 436 } else { 437 return { type: 'and', children: [tree] }; 438 } 439 } 440 441 // ====== è¾å ¥æ¨¡å¼åæ¢ ====== 442 // 忢æ¡ä»¶è¾å ¥æ¨¡å¼ï¼æå¨/æå»ºå¨ï¼ 443 function switchConditionInputMode(mode, skipConfirm = false) { 444 const currentCondition = document.getElementById('condition').value.trim(); 445 if (!currentCondition) { skipConfirm = true; } 446 if (!skipConfirm) { 447 let msg = ""; 448 if (mode === 'builder') { 449 msg = "å°å½åé»è¾å¤ææ¡ä»¶è½¬åè³æ¡ä»¶æå»ºå¨æ¶ä¼å 级åµå¥å¯è½ä¼æ··ä¹±ï¼ä¸æ¯æè½¬åå符 */%ï¼æ¯å¦ç»§ç»åæ¢å°æ¡ä»¶æå»ºå¨è¾å ¥ï¼"; 450 } else if (mode === 'manual') { 451 msg = "æ¯å¦è½¬åè³æå¨è¾å ¥ï¼"; 452 } 453 if (!confirm(msg)) return false; 454 } 455 const manualDiv = document.getElementById('manualConditionInput'); 456 const builderDiv = document.getElementById('builderConditionInput'); 457 if (mode === 'manual') { 458 if (builderRootCondition) { 459 const raw = builderGenerateConditionText(builderRootCondition); 460 document.getElementById('condition').value = raw; 461 } 462 builderDiv.classList.add('hidden'); 463 manualDiv.classList.remove('hidden'); 464 } else { 465 let manualStr = document.getElementById('condition').value.trim(); 466 if (manualStr) { 467 try { 468 builderRootCondition = parseManualCondition(manualStr); 469 builderRender(); 470 } catch (err) { 471 alert("æå¨æ¡ä»¶è½¬æ¢å¤±è´¥ï¼" + err.message); 472 document.querySelector('input[name="conditionInputMode"][value="manual"]').checked = true; 473 return false;
474 } 475 } else { 476 builderRootCondition = builderCreateCondition('and', []); 477 builderRender(); 478 } 479 manualDiv.classList.add('hidden'); 480 builderDiv.classList.remove('hidden'); 481 } 482 currentConditionInputMode = mode; 483 return true; 484 } 485 486 // ====== äºä»¶ç»å®ä¸åå§å ====== 487 // ç»å®è¾å ¥æ¨¡å¼åæ¢äºä»¶ 488 function bindModeSwitch() { 489 document.querySelectorAll('input[name="conditionInputMode"]').forEach(radio => { 490 radio.addEventListener('change', function (e) { 491 const newMode = e.target.value; 492 if (!switchConditionInputMode(newMode)) { 493 document.querySelector(`input[name="conditionInputMode"][value="${currentConditionInputMode}"]`).checked = true; 494 } 495 }); 496 }); 497 } 498 // ç»å®å¡åè¾å ¥çå¬ï¼å¡åååæ¶å·æ°ä¸æ 499 function bindCardNameListener() { 500 if (window.UIUtils && window.UIUtils.setupCardNameInputListener) { 501 window.UIUtils.setupCardNameInputListener(); 502 } 503 // çå¬å¡ååæ°éè¾å ¥ååï¼å®æ¶å·æ°æ¡ä»¶æå»ºå¨ä¸æ 504 const cardInputs = document.getElementById('cardInputs'); 505 if (cardInputs) { 506 cardInputs.addEventListener('input', function (e) { 507 if (e.target && e.target.id && 508 (e.target.id.startsWith('cardName') || e.target.id.startsWith('card'))) { 509 if (document.querySelector('input[name="conditionInputMode"]:checked')?.value === 'builder') { 510 builderRender(); 511 } 512 } 513 }); 514 } 515 } 516 // åå§åæ¡ä»¶æå»ºå¨ 517 function init() { 518 builderRootCondition = builderCreateCondition('and', []); 519 builderRender(); 520 switchConditionInputMode('manual', true); 521 bindModeSwitch(); 522 bindCardNameListener(); 523 } 524 525 // ====== 坹夿¥å£ ====== 526 // æä¾åå§åãè·å/设置æ¡ä»¶çæ¥å£ 527 global.ConditionBuilder = { 528 init, 529 getConditionInputMode: function () { 530 return document.querySelector('input[name="conditionInputMode"]:checked')?.value || 'manual'; 531 }, 532 getBuilderConditionData: function () { 533 return builderRootCondition ? JSON.stringify(builderRootCondition) : ''; 534 }, 535 setBuilderConditionData: function (json) { 536 try { 537 builderRootCondition = JSON.parse(json); 538 builderRender(); 539 } catch (e) { 540 builderRootCondition = builderCreateCondition('and', []); 541 builderRender(); 542 } 543 } 544 }; 545})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.