Files
aurora-admin/frameworks/cascader.html
T

197 lines
7.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora Admin · Cascader (H5)</title>
<link rel="stylesheet" href="../.design_library/aurora-admin/colors_and_type.css">
<link rel="stylesheet" href="./Cascader.css">
<style>
.demo { padding: 24px; background: #F5F7FA; font-family: var(--font-family); }
h1 { font-size: 20px; margin: 0 0 4px; color: #1F2329; }
.sub { color: #8C8C8C; margin: 0 0 24px; font-size: 12px; }
.demo-block { background: #fff; border-radius: 8px; padding: 24px; margin-bottom: 20px; }
.demo-block > .hint { color: #8C8C8C; font-size: 12px; margin: 0 0 12px; }
</style>
</head>
<body>
<div class="demo">
<h1>Aurora Admin · Cascader 组件(纯 H5)</h1>
<p class="sub">对齐 Cascader 规范:多级下拉、搜索、路径显示、多选</p>
<div class="demo-block">
<p class="hint">单选 + 搜索</p>
<div class="aa-cascader" data-cascader data-multiple="false"></div>
</div>
<div class="demo-block">
<p class="hint">多选</p>
<div class="aa-cascader" data-cascader data-multiple="true"></div>
</div>
</div>
<script>
const OPTIONS = [
{ value: 'zj', label: '浙江', children: [
{ value: 'hz', label: '杭州', children: [ { value: 'xh', label: '西湖区' }, { value: 'bj', label: '滨江区' } ] },
{ value: 'wz', label: '温州', children: [ { value: 'lc', label: '鹿城区' } ] }
] },
{ value: 'js', label: '江苏', children: [
{ value: 'nj', label: '南京', children: [ { value: 'xk', label: '玄武区' } ] }
] }
];
function pathLabels(values) {
let nodes = OPTIONS, labels = [];
for (const v of values) {
const n = nodes.find(x => x.value === v);
if (!n) break;
labels.push(n.label); nodes = n.children || [];
}
return labels;
}
function findNode(values) {
let nodes = OPTIONS, node = null;
for (const v of values) {
node = nodes.find(x => x.value === v);
if (!node) return null;
nodes = node.children || [];
}
return node;
}
function flatten() {
const out = [];
(function walk(list, acc) {
list.forEach(n => {
const p = [...acc, n.value];
if (n.children && n.children.length) walk(n.children, p);
else out.push(p);
});
})(OPTIONS, []);
return out;
}
function initCascader(root) {
const multiple = root.dataset.multiple === 'true';
let open = false;
let activePath = [];
let query = '';
// single: value = 数组路径或 null ; multiple: value = 路径数组的数组
let value = multiple ? [] : null;
const trigger = document.createElement('div');
trigger.className = 'aa-cascader-trigger';
function renderTrigger() {
if (multiple) {
if (!value.length) { trigger.innerHTML = '<span class="placeholder">请选择</span><span class="caret">▾</span>'; }
else {
trigger.innerHTML = '<span class="aa-cascader-tags">' +
value.map(p => `<span class="aa-cascader-tag">${pathLabels(p).join(' / ')}</span>`).join('') +
'</span><span class="caret">▾</span>';
}
} else {
trigger.innerHTML = (value ? `<span>${pathLabels(value).join(' / ')}</span>` : '<span class="placeholder">请选择</span>') + '<span class="caret">▾</span>';
}
trigger.classList.toggle('is-open', open);
}
function renderPanel() {
let panel = root.querySelector('.aa-cascader-panel');
if (!panel) { panel = document.createElement('div'); panel.className = 'aa-cascader-panel'; root.appendChild(panel); }
if (query) {
const results = flatten().filter(p => pathLabels(p).join('/').includes(query));
panel.innerHTML = `<div class="aa-cascader-results">` +
(results.length ? results.map(p => `<div class="aa-cascader-result" data-path='${JSON.stringify(p)}'>${pathLabels(p).join(' / ')}</div>`).join('') : '<div class="aa-cascader-empty">无匹配结果</div>') +
`</div>`;
panel.querySelectorAll('.aa-cascader-result').forEach(r => {
r.addEventListener('click', () => {
const p = JSON.parse(r.dataset.path);
if (multiple) {
const s = value.map(x => JSON.stringify(x));
const k = JSON.stringify(p);
if (s.includes(k)) value = value.filter(x => JSON.stringify(x) !== k);
else value = [...value, p];
renderTrigger();
} else { value = p; open = false; renderTrigger(); renderPanel(); }
});
});
return;
}
// 列视图
let html = '';
let levelNodes = OPTIONS, path = [];
for (let lv = 0; lv <= activePath.length; lv++) {
const col = levelNodes;
html += '<div class="aa-cascader-col">';
col.forEach(n => {
const isActive = activePath[lv] === n.value;
const hasChild = n.children && n.children.length;
let box = '';
if (multiple && !hasChild) {
const sel = value.some(p => JSON.stringify(p) === JSON.stringify([...path, n.value].slice(0, [...path, n.value].length)));
const checked = value.some(p => JSON.stringify(p) === JSON.stringify([...path, n.value]));
box = `<span class="box">${checked ? '✓' : ''}</span>`;
}
html += `<div class="aa-cascader-opt ${isActive ? 'is-active' : ''}" data-lv="${lv}" data-val="${n.value}">${box}<span>${n.label}</span>${hasChild ? '<span class="arrow">›</span>' : ''}</div>`;
});
html += '</div>';
if (lv < activePath.length) {
const next = levelNodes.find(x => x.value === activePath[lv]);
path = [...path, activePath[lv]];
levelNodes = next.children || [];
} else break;
}
panel.innerHTML = html;
panel.querySelectorAll('.aa-cascader-opt').forEach(opt => {
opt.addEventListener('click', () => {
const lv = +opt.dataset.lv, val = opt.dataset.val;
const node = findNode([...activePath.slice(0, lv), val]);
const full = [...activePath.slice(0, lv), val];
if (node && node.children && node.children.length) {
activePath = full; renderPanel();
} else {
if (multiple) {
const k = JSON.stringify(full);
value = value.some(p => JSON.stringify(p) === k) ? value.filter(p => JSON.stringify(p) !== k) : [...value, full];
renderTrigger();
} else { value = full; open = false; renderTrigger(); renderPanel(); }
}
});
});
}
trigger.addEventListener('click', (e) => {
e.stopPropagation();
open = !open; renderTrigger(); renderPanel();
});
root.appendChild(trigger);
root.addEventListener('click', (e) => {
const q = e.target.closest('.aa-cascader-search input');
if (q) return;
});
// 搜索框(filterable)
const search = document.createElement('div');
search.className = 'aa-cascader-search';
search.style.display = 'none';
search.innerHTML = '<input placeholder="搜索"><div class="aa-cascader-results"></div>';
search.querySelector('input').addEventListener('input', (e) => { query = e.target.value; renderPanel(); });
root.appendChild(search);
trigger.addEventListener('click', () => {
search.style.display = open ? 'block' : 'none';
if (open) search.querySelector('input').focus();
});
document.addEventListener('click', (e) => {
if (!root.contains(e.target)) { open = false; query = ''; renderTrigger(); renderPanel(); search.style.display = 'none'; }
});
renderTrigger();
}
document.querySelectorAll('[data-cascader]').forEach(initCascader);
</script>
</body>
</html>