| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>OA 条件分支 · 表单字段驱动 Demo</title>
- <style>
- :root {
- --primary: #10b981;
- --primary-dark: #059669;
- --primary-bg: #ecfdf5;
- --text: #0f172a;
- --text-2: #334155;
- --muted: #64748b;
- --border: #e2e8f0;
- --border-strong: #cbd5e1;
- --bg: #f1f5f9;
- --warn: #d97706;
- --radius: 8px;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- font: 14px/1.5 Inter, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
- color: var(--text);
- background: var(--bg);
- }
- .page { max-width: 1180px; margin: 0 auto; padding: 16px 20px 40px; }
- .banner {
- display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: flex-start;
- justify-content: space-between;
- padding: 12px 16px; margin-bottom: 14px;
- background: #fff; border: 1px solid var(--border); border-radius: 10px;
- }
- .banner h1 { margin: 0 0 4px; font-size: 16px; }
- .banner p { margin: 0; color: var(--muted); font-size: 13px; max-width: 720px; }
- .tag {
- display: inline-block; padding: 2px 8px; border-radius: 999px;
- background: var(--primary-bg); color: var(--primary-dark);
- font-size: 12px; font-weight: 600;
- }
- .panel {
- background: #fff; border: 1px solid var(--border); border-radius: 10px;
- overflow: hidden;
- }
- .toolbar {
- display: flex; align-items: center; justify-content: space-between;
- padding: 10px 14px; border-bottom: 1px solid var(--border);
- }
- .btn {
- display: inline-flex; align-items: center; gap: 6px;
- height: 34px; padding: 0 12px;
- border: 1px solid var(--border); border-radius: 8px;
- background: #fff; color: var(--text-2); cursor: pointer; font-size: 13px;
- }
- .btn:hover { border-color: var(--primary); color: var(--primary-dark); }
- .btn.primary {
- background: var(--primary); border-color: var(--primary); color: #fff;
- }
- .btn.primary:hover { background: var(--primary-dark); }
- .btn.sm { height: 28px; padding: 0 8px; font-size: 12px; }
- .tabs {
- display: flex; gap: 0; border-bottom: 1px solid var(--border);
- padding: 0 12px; background: #fafafa;
- }
- .tab {
- border: 0; background: transparent; padding: 12px 14px;
- color: var(--muted); cursor: pointer; font-size: 13px;
- border-bottom: 2px solid transparent; margin-bottom: -1px;
- }
- .tab.active {
- color: var(--primary-dark); font-weight: 600;
- border-bottom-color: var(--primary);
- }
- .step-body { display: none; min-height: 520px; }
- .step-body.show { display: block; }
- /* ===== 表单设计 ===== */
- .form-layout {
- display: grid;
- grid-template-columns: 200px 1fr 280px;
- min-height: 520px;
- }
- .palette, .props {
- background: #fafafa; padding: 12px;
- border-right: 1px solid var(--border);
- }
- .props { border-right: 0; border-left: 1px solid var(--border); }
- .palette h3, .props h3 {
- margin: 0 0 10px; font-size: 12px; color: var(--muted);
- font-weight: 600; letter-spacing: .02em;
- }
- .palette-item {
- padding: 8px 10px; margin-bottom: 6px;
- background: #fff; border: 1px solid var(--border); border-radius: 6px;
- font-size: 13px; color: var(--text-2);
- }
- .canvas { padding: 20px 24px; }
- .canvas-title { font-size: 15px; font-weight: 600; margin-bottom: 16px; }
- .field-card {
- position: relative;
- padding: 12px 14px; margin-bottom: 10px;
- border: 1px solid var(--border); border-radius: 8px;
- cursor: pointer; background: #fff;
- }
- .field-card:hover { border-color: #a7f3d0; }
- .field-card.selected {
- border-color: var(--primary);
- box-shadow: 0 0 0 2px rgba(16, 185, 129, .15);
- }
- .field-card .label { font-size: 12px; color: var(--muted); margin-bottom: 6px; }
- .field-card .fake-input {
- height: 36px; border: 1px solid var(--border); border-radius: 8px;
- background: #f8fafc; display: flex; align-items: center; padding: 0 10px;
- color: var(--muted); font-size: 13px;
- }
- .field-card .badge {
- position: absolute; top: 8px; right: 8px;
- font-size: 11px; padding: 1px 6px; border-radius: 4px;
- background: var(--primary-bg); color: var(--primary-dark);
- display: none;
- }
- .field-card.in-flow .badge { display: inline-block; }
- .prop-row { margin-bottom: 14px; }
- .prop-row label {
- display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px;
- }
- .prop-row input, .prop-row select {
- width: 100%; height: 34px; padding: 0 10px;
- border: 1px solid var(--border); border-radius: 8px; font-size: 13px;
- }
- .switch-row {
- display: flex; align-items: flex-start; gap: 10px;
- padding: 12px; border: 1px dashed #a7f3d0; border-radius: 8px;
- background: var(--primary-bg);
- }
- .switch-row input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--primary); }
- .switch-row .t { font-size: 13px; font-weight: 600; color: var(--primary-dark); }
- .switch-row .d { font-size: 12px; color: var(--muted); margin-top: 2px; }
- .hint {
- margin-top: 12px; padding: 10px 12px; font-size: 12px; color: var(--muted);
- background: #fff; border: 1px solid var(--border); border-radius: 8px;
- }
- /* ===== 流程设计 ===== */
- .flow-layout {
- display: grid; grid-template-columns: 1fr 340px; min-height: 520px;
- }
- .flow-tree {
- padding: 28px 24px; display: flex; flex-direction: column; align-items: center;
- background: linear-gradient(180deg, #f8fafc, #fff);
- }
- .node {
- width: 220px; border-radius: 10px; background: #fff;
- border: 1px solid var(--border); overflow: hidden; cursor: pointer;
- box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
- }
- .node:hover { border-color: #a7f3d0; }
- .node.active { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(16,185,129,.2); }
- .node .hd {
- padding: 6px 10px; font-size: 12px; font-weight: 600; color: #fff;
- }
- .node.start .hd { background: #64748b; }
- .node.approval .hd { background: var(--primary); }
- .node.condition .hd { background: #f59e0b; }
- .node.end .hd { background: #94a3b8; }
- .node .bd { padding: 10px 12px; font-size: 12px; color: var(--muted); }
- .vline {
- width: 2px; height: 18px; background: var(--border-strong);
- }
- .plus {
- width: 24px; height: 24px; border-radius: 50%;
- border: 1px solid var(--border); background: #fff;
- display: flex; align-items: center; justify-content: center;
- color: var(--primary-dark); font-weight: 700; font-size: 14px;
- }
- .branches {
- display: flex; gap: 24px; align-items: flex-start; margin-top: 8px;
- position: relative;
- }
- .branches::before {
- content: ""; position: absolute; top: 0; left: 25%; right: 25%;
- height: 2px; background: var(--border-strong);
- }
- .branch {
- display: flex; flex-direction: column; align-items: center; width: 200px;
- }
- .branch-tag {
- margin: 8px 0; padding: 4px 10px; border-radius: 999px;
- background: #fffbeb; border: 1px solid #fde68a;
- color: var(--warn); font-size: 12px; font-weight: 600;
- max-width: 190px; text-align: center; cursor: pointer;
- }
- .branch-tag.default { background: #f1f5f9; border-color: var(--border); color: var(--muted); }
- .branch-tag.active {
- outline: 2px solid var(--primary); outline-offset: 2px;
- }
- .drawer {
- border-left: 1px solid var(--border); background: #fff; padding: 14px 16px;
- display: flex; flex-direction: column; gap: 12px;
- }
- .drawer h3 { margin: 0; font-size: 15px; }
- .drawer .sub { margin: 0; font-size: 12px; color: var(--muted); }
- .legacy {
- padding: 10px; border-radius: 8px; background: #fff7ed; border: 1px solid #fed7aa;
- font-size: 12px; color: #9a3412;
- }
- .legacy code {
- display: block; margin-top: 6px; padding: 6px 8px;
- background: #fff; border-radius: 6px; font-family: ui-monospace, monospace;
- color: var(--text);
- }
- .cond-box {
- border: 1px solid var(--border); border-radius: 8px; padding: 12px;
- background: #fafafa;
- }
- .cond-box .title {
- display: flex; justify-content: space-between; align-items: center;
- margin-bottom: 10px; font-weight: 600; font-size: 13px;
- }
- .rule-row {
- display: grid; grid-template-columns: 1fr auto 1fr;
- gap: 6px; margin-bottom: 8px; align-items: center;
- }
- .rule-row select, .rule-row input {
- height: 32px; border: 1px solid var(--border); border-radius: 6px;
- padding: 0 8px; font-size: 12px; background: #fff; width: 100%;
- }
- .rule-row select:disabled, .rule-row input:disabled {
- background: #f1f5f9; color: var(--muted);
- }
- .preview {
- margin-top: 8px; padding: 8px 10px; border-radius: 6px;
- background: var(--primary-bg); color: var(--primary-dark);
- font-size: 12px;
- }
- .empty-tip {
- padding: 10px; font-size: 12px; color: var(--warn);
- background: #fffbeb; border-radius: 6px; border: 1px dashed #fde68a;
- }
- .footer-note {
- margin-top: 14px; font-size: 12px; color: var(--muted); text-align: center;
- }
- </style>
- </head>
- <body>
- <div class="page">
- <div class="banner">
- <div>
- <h1>条件分支:从表单字段点选配置(交互 Demo)</h1>
- <p>
- 对照现状「手写 <code>{days} > 5</code>」。本 Demo 演示:在<strong>表单设计</strong>勾选参与分支的字段 →
- 在<strong>流程设计</strong>用「字段 + 运算符 + 值」配置条件,无需记变量名。
- </p>
- </div>
- <span class="tag">Fresh Mint · 可点击</span>
- </div>
- <div class="panel">
- <div class="toolbar">
- <button class="btn" type="button" disabled>← 返回</button>
- <div style="display:flex;gap:8px;">
- <button class="btn" type="button">保存草稿</button>
- <button class="btn primary" type="button">发布</button>
- </div>
- </div>
- <div class="tabs">
- <button class="tab" type="button" data-step="basic">1 基础设置</button>
- <button class="tab active" type="button" data-step="form">2 表单设计</button>
- <button class="tab" type="button" data-step="flow">3 流程设计</button>
- <button class="tab" type="button" data-step="advanced">4 高级设置</button>
- </div>
- <!-- 表单设计 -->
- <div id="step-form" class="step-body show">
- <div class="form-layout">
- <aside class="palette">
- <h3>控件库</h3>
- <div class="palette-item">单行输入</div>
- <div class="palette-item">数字</div>
- <div class="palette-item">金额</div>
- <div class="palette-item">单选</div>
- <div class="palette-item">日期</div>
- <div class="palette-item">附件</div>
- </aside>
- <main class="canvas">
- <div class="canvas-title">请假申请 · 表单画布</div>
- <div
- class="field-card selected in-flow"
- data-field="days"
- data-title="请假天数"
- data-type="number"
- onclick="selectField(this)"
- >
- <span class="badge">用于分支</span>
- <div class="label">请假天数 *</div>
- <div class="fake-input">请输入数字</div>
- </div>
- <div
- class="field-card"
- data-field="reason"
- data-title="请假事由"
- data-type="text"
- onclick="selectField(this)"
- >
- <span class="badge">用于分支</span>
- <div class="label">请假事由 *</div>
- <div class="fake-input">请输入</div>
- </div>
- <div
- class="field-card in-flow"
- data-field="leaveType"
- data-title="请假类型"
- data-type="select"
- onclick="selectField(this)"
- >
- <span class="badge">用于分支</span>
- <div class="label">请假类型</div>
- <div class="fake-input">事假 / 病假 / 年假</div>
- </div>
- <div
- class="field-card"
- data-field="attach"
- data-title="附件"
- data-type="attachment"
- onclick="selectField(this)"
- >
- <span class="badge">用于分支</span>
- <div class="label">附件</div>
- <div class="fake-input">上传附件</div>
- </div>
- </main>
- <aside class="props">
- <h3>字段属性</h3>
- <div class="prop-row">
- <label>字段标题</label>
- <input id="prop-title" value="请假天数" />
- </div>
- <div class="prop-row">
- <label>字段标识(系统用,用户无需关心)</label>
- <input id="prop-field" value="days" readonly style="color:var(--muted)" />
- </div>
- <div class="switch-row">
- <input id="prop-in-flow" type="checkbox" checked onchange="toggleInFlow()" />
- <div>
- <div class="t">参与流程条件分支</div>
- <div class="d">勾选后,可在「3 流程设计」的分支条件里选到此字段,无需手写变量名。</div>
- </div>
- </div>
- <div class="hint" id="prop-hint">
- 数字 / 金额 / 单选 / 日期等适合做条件;附件类控件一般不可勾选(本 Demo 里附件勾选会提示)。
- </div>
- </aside>
- </div>
- </div>
- <!-- 流程设计 -->
- <div id="step-flow" class="step-body">
- <div class="flow-layout">
- <div class="flow-tree">
- <div class="node start">
- <div class="hd">发起人</div>
- <div class="bd">所有人可发起</div>
- </div>
- <div class="vline"></div>
- <div class="plus">+</div>
- <div class="vline"></div>
- <div class="node condition active" onclick="focusCondition()">
- <div class="hd">条件分支</div>
- <div class="bd" id="cond-summary">2 个分支 · 点击右侧配置</div>
- </div>
- <div class="branches">
- <div class="branch">
- <span class="branch-tag active" id="tag-0" onclick="selectBranch(0)">请假天数 > 3</span>
- <div class="vline"></div>
- <div class="node approval">
- <div class="hd">审批人</div>
- <div class="bd">总经理</div>
- </div>
- <div class="vline"></div>
- <div class="node end"><div class="hd">结束</div><div class="bd">流程结束</div></div>
- </div>
- <div class="branch">
- <span class="branch-tag default" id="tag-1" onclick="selectBranch(1)">默认 · 其他情况</span>
- <div class="vline"></div>
- <div class="node approval">
- <div class="hd">审批人</div>
- <div class="bd">部门负责人</div>
- </div>
- <div class="vline"></div>
- <div class="node end"><div class="hd">结束</div><div class="bd">流程结束</div></div>
- </div>
- </div>
- </div>
- <aside class="drawer">
- <h3>配置条件分支</h3>
- <p class="sub">用表单里勾选了「参与流程条件分支」的字段来搭建规则</p>
- <div class="legacy">
- <strong>现状(不好用)</strong>
- <code>{days} > 3</code>
- 普通用户不知道 days 从哪来、运算符怎么写。
- </div>
- <div class="prop-row">
- <label>当前编辑的分支</label>
- <select id="branch-pick" onchange="selectBranch(+this.value)">
- <option value="0">分支 1 · 大于 3 天</option>
- <option value="1">分支 2 · 默认</option>
- </select>
- </div>
- <div class="cond-box" id="rule-editor">
- <div class="title">
- <span>条件规则</span>
- <button class="btn sm" type="button" onclick="addRule()" id="btn-add-rule">+ 且条件</button>
- </div>
- <div id="rules"></div>
- <div class="preview" id="rule-preview">预览:请假天数 > 3</div>
- </div>
- <div class="prop-row">
- <label><input type="checkbox" id="is-default" onchange="toggleDefault()" /> 设为默认分支(其他条件都不满足时走这里)</label>
- </div>
- <div class="empty-tip" id="no-fields-tip" style="display:none">
- 暂无可用字段。请先到「2 表单设计」勾选至少一个「参与流程条件分支」的字段。
- </div>
- <button class="btn primary" type="button" style="align-self:flex-start">保存节点配置</button>
- </aside>
- </div>
- </div>
- <div id="step-basic" class="step-body" style="padding:48px;text-align:center;color:var(--muted)">
- (本 Demo 不展开)基础设置:图标、名称、说明…
- </div>
- <div id="step-advanced" class="step-body" style="padding:48px;text-align:center;color:var(--muted)">
- (本 Demo 不展开)高级设置:…</div>
- </div>
- <p class="footer-note">
- 文件位置:<code>docs/mockups/oa-approval-condition-from-form-demo.html</code>
- · 试用:切到「2 表单设计」勾选字段 → 再切「3 流程设计」拉下拉里应能看到标题而非 variable 名
- </p>
- </div>
- <script>
- /** 画布字段状态 */
- const fields = {
- days: { title: '请假天数', type: 'number', inFlow: true },
- reason: { title: '请假事由', type: 'text', inFlow: false },
- leaveType: { title: '请假类型', type: 'select', inFlow: true, options: ['事假', '病假', '年假'] },
- attach: { title: '附件', type: 'attachment', inFlow: false }
- }
- let selectedField = 'days'
- let branchIdx = 0
- const branches = [
- {
- name: '大于 3 天',
- isDefault: false,
- rules: [{ field: 'days', op: '>', value: '3' }]
- },
- {
- name: '默认',
- isDefault: true,
- rules: []
- }
- ]
- const OPS = {
- number: [
- { v: '>', t: '大于' },
- { v: '>=', t: '大于等于' },
- { v: '<', t: '小于' },
- { v: '<=', t: '小于等于' },
- { v: '==', t: '等于' },
- { v: '!=', t: '不等于' }
- ],
- text: [
- { v: '==', t: '等于' },
- { v: '!=', t: '不等于' },
- { v: 'contains', t: '包含' }
- ],
- select: [
- { v: '==', t: '等于' },
- { v: '!=', t: '不等于' }
- ]
- }
- document.querySelectorAll('.tab').forEach((tab) => {
- tab.addEventListener('click', () => {
- document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'))
- tab.classList.add('active')
- const step = tab.dataset.step
- document.querySelectorAll('.step-body').forEach((el) => el.classList.remove('show'))
- const body = document.getElementById('step-' + step)
- if (body) body.classList.add('show')
- if (step === 'flow') renderRules()
- })
- })
- function selectField(el) {
- document.querySelectorAll('.field-card').forEach((c) => c.classList.remove('selected'))
- el.classList.add('selected')
- selectedField = el.dataset.field
- const f = fields[selectedField]
- document.getElementById('prop-title').value = f.title
- document.getElementById('prop-field').value = selectedField
- const cb = document.getElementById('prop-in-flow')
- cb.checked = !!f.inFlow
- cb.disabled = f.type === 'attachment'
- const hint = document.getElementById('prop-hint')
- if (f.type === 'attachment') {
- hint.textContent = '附件不适合做条件判断,已禁用「参与流程条件分支」。'
- } else {
- hint.textContent = '勾选后出现在「3 流程设计」的字段下拉里,展示标题「' + f.title + '」,内部仍用 field=' + selectedField + '。'
- }
- }
- function toggleInFlow() {
- const f = fields[selectedField]
- if (f.type === 'attachment') {
- document.getElementById('prop-in-flow').checked = false
- alert('附件类型不能参与条件分支')
- return
- }
- f.inFlow = document.getElementById('prop-in-flow').checked
- const card = document.querySelector('.field-card[data-field="' + selectedField + '"]')
- card.classList.toggle('in-flow', f.inFlow)
- }
- document.getElementById('prop-title').addEventListener('input', (e) => {
- fields[selectedField].title = e.target.value
- const card = document.querySelector('.field-card[data-field="' + selectedField + '"]')
- card.querySelector('.label').textContent = e.target.value + (selectedField === 'days' || selectedField === 'reason' ? ' *' : '')
- renderRules()
- })
- function flowFields() {
- return Object.keys(fields)
- .filter((k) => fields[k].inFlow && fields[k].type !== 'attachment')
- .map((k) => ({ key: k, ...fields[k] }))
- }
- function selectBranch(idx) {
- branchIdx = idx
- document.getElementById('branch-pick').value = String(idx)
- document.getElementById('is-default').checked = !!branches[idx].isDefault
- document.querySelectorAll('.branch-tag').forEach((t, i) => {
- t.classList.toggle('active', i === idx)
- })
- renderRules()
- }
- function focusCondition() {
- selectBranch(branchIdx)
- }
- function toggleDefault() {
- const on = document.getElementById('is-default').checked
- branches.forEach((b, i) => { b.isDefault = on && i === branchIdx })
- if (on) branches[branchIdx].rules = []
- updateTags()
- renderRules()
- }
- function opLabel(op) {
- const map = { '>': '大于', '>=': '大于等于', '<': '小于', '<=': '小于等于', '==': '等于', '!=': '不等于', contains: '包含' }
- return map[op] || op
- }
- function renderRules() {
- const box = document.getElementById('rules')
- const tip = document.getElementById('no-fields-tip')
- const editor = document.getElementById('rule-editor')
- const b = branches[branchIdx]
- const available = flowFields()
- if (!available.length) {
- tip.style.display = 'block'
- } else {
- tip.style.display = 'none'
- }
- if (b.isDefault) {
- editor.style.opacity = '0.45'
- document.getElementById('btn-add-rule').disabled = true
- box.innerHTML = '<div style="font-size:12px;color:var(--muted)">默认分支无需配置条件</div>'
- document.getElementById('rule-preview').textContent = '预览:其他条件均不满足时走此分支'
- updateTags()
- return
- }
- editor.style.opacity = '1'
- document.getElementById('btn-add-rule').disabled = false
- if (!b.rules.length) {
- b.rules.push({ field: available[0] ? available[0].key : '', op: '>', value: '' })
- }
- box.innerHTML = ''
- b.rules.forEach((rule, i) => {
- const fMeta = fields[rule.field] || available[0]
- const type = fMeta ? fMeta.type : 'number'
- const ops = OPS[type] || OPS.number
- const row = document.createElement('div')
- row.className = 'rule-row'
- const selF = document.createElement('select')
- if (!available.length) {
- selF.innerHTML = '<option value="">无可用字段</option>'
- selF.disabled = true
- } else {
- available.forEach((f) => {
- const opt = document.createElement('option')
- opt.value = f.key
- opt.textContent = f.title
- if (f.key === rule.field) opt.selected = true
- selF.appendChild(opt)
- })
- }
- selF.onchange = () => {
- rule.field = selF.value
- const t = fields[rule.field].type
- rule.op = (OPS[t] || OPS.number)[0].v
- rule.value = ''
- renderRules()
- }
- const selOp = document.createElement('select')
- ops.forEach((o) => {
- const opt = document.createElement('option')
- opt.value = o.v
- opt.textContent = o.t
- if (o.v === rule.op) opt.selected = true
- selOp.appendChild(opt)
- })
- selOp.onchange = () => { rule.op = selOp.value; updateTags() }
- let valCtrl
- if (type === 'select' && fields[rule.field].options) {
- valCtrl = document.createElement('select')
- fields[rule.field].options.forEach((o) => {
- const opt = document.createElement('option')
- opt.value = o
- opt.textContent = o
- if (o === rule.value) opt.selected = true
- valCtrl.appendChild(opt)
- })
- if (!rule.value) rule.value = fields[rule.field].options[0]
- valCtrl.onchange = () => { rule.value = valCtrl.value; updateTags() }
- } else {
- valCtrl = document.createElement('input')
- valCtrl.placeholder = type === 'number' ? '数值' : '比较值'
- valCtrl.value = rule.value
- valCtrl.oninput = () => { rule.value = valCtrl.value; updateTags() }
- }
- row.appendChild(selF)
- row.appendChild(selOp)
- row.appendChild(valCtrl)
- box.appendChild(row)
- if (i < b.rules.length - 1) {
- const and = document.createElement('div')
- and.style.cssText = 'font-size:11px;color:var(--muted);margin:-2px 0 6px;text-align:center'
- and.textContent = '且(AND)'
- box.appendChild(and)
- }
- })
- updateTags()
- }
- function addRule() {
- const available = flowFields()
- if (!available.length || branches[branchIdx].isDefault) return
- branches[branchIdx].rules.push({
- field: available[0].key,
- op: (OPS[available[0].type] || OPS.number)[0].v,
- value: ''
- })
- renderRules()
- }
- function updateTags() {
- const b0 = branches[0]
- const parts = (b0.rules || []).map((r) => {
- const title = (fields[r.field] && fields[r.field].title) || r.field
- return title + ' ' + opLabel(r.op) + ' ' + (r.value === '' ? '?' : r.value)
- })
- const text0 = parts.length ? parts.join(' 且 ') : '未配置'
- document.getElementById('tag-0').textContent = text0
- document.getElementById('tag-1').textContent = '默认 · 其他情况'
- document.getElementById('cond-summary').textContent = '2 个分支 · ' + text0
- if (!branches[branchIdx].isDefault) {
- const cur = branches[branchIdx]
- const p = (cur.rules || []).map((r) => {
- const title = (fields[r.field] && fields[r.field].title) || r.field
- return title + ' ' + opLabel(r.op) + ' ' + (r.value === '' ? '?' : r.value)
- })
- document.getElementById('rule-preview').textContent =
- '预览:' + (p.join(' 且 ') || '未配置') +
- ' | 引擎侧仍存为 {field, op, value},等价公式 ' +
- (cur.rules[0] ? '{' + cur.rules[0].field + '} ' + cur.rules[0].op + ' ' + cur.rules[0].value : '')
- }
- }
- // init
- renderRules()
- </script>
- </body>
- </html>
|