下拉框改为自绘列表;修复新建软件缺排序字段
用户两个反馈: ① 「排序那个怎么删了?排序还得留着呀」 ② 「所有下拉框拉下来之后全是灰色,不好看,重新设计」 一、排序字段(是误解,但确实有缺陷) 排序字段没有被删,一直在 if ($esw) 分支里渲染。 用户看到"没有",是因为他打开的是「添加软件」—— 新建时 $esw 为空,整块不渲染,所以确实没有这一项。 这是真实缺陷:新建软件时无法指定排序。 修法: - 把排序字段移出 if ($esw),新建/编辑都显示 - 新建时默认值 = 当前最大 sort_order + 1(排到最后) - 后端 sw_add 改为优先用表单值(用 isset 判断, 用户明确填 0 是合法值——排最前面,不能被当成"没填") 与「下载地址」不同:排序是真实会被用到的字段 (前台软件列表、顶栏三横线菜单都按它排),不能删。 二、下拉框重设计 为什么必须自绘:原生 select 的展开列表由浏览器绘制, CSS 只能改 option 的底色,改不了圆角/行高/hover 高亮, 各平台样式还不一样(Windows 是灰方框)。用户说的"全是灰色"就是这个。 但不重写 HTML:全站 16 个 select,逐个改容易漏, 也会破坏 onchange 提交这类内联逻辑。 做法:原生 select 保留(视觉隐藏,仍参与提交与 change 事件), JS 在旁生成按钮 + 浮层列表。 - 浮层用 position:fixed 挂 body,避免被 overflow:hidden 裁(踩过) - 位置自动避让视口,下方不够翻上方 - 支持键盘:上下键 Home End Enter Esc - 选中项带对勾 - 无 JS 时原生 select 仍可用 配色踩坑:选中项最初用 var(--brand-soft),而该变量会被软件主题色覆盖 —— 同步日历主题色 #ff3f44 时 --brand-soft 成了 #ffe8e9(粉红), 下拉里一片粉很难看。下拉是工具性控件,不该跟软件换色, 改用中性色:#eef1f5 选中 / #f1f3f6 hover,四个软件下完全一致。 验证(真实页面实测): - 2 个 select 均被增强,原生已隐藏 - 面板白底 rgb(255,255,255)、圆角 11px、双层柔和阴影、7 个列表项 - 选中项 rgb(238,241,245)、hover rgb(241,243,246)、带对勾 - 四个软件(#0057b8/#ff3f44/#a556f7/#3571fb)下选中项底色完全相同, 不再随主题色变粉 - 键盘下方向键三次 → 高亮「处理中」,底色正确 - 选择后 selectedIndex/value 同步、面板自动关闭、change 事件已触发 - 浮层 fixed 定位、完整可见、不被裁切 - 排序字段 3 处(软件/分类/状态)齐全;5 个路由 200
This commit is contained in:
+216
@@ -1109,6 +1109,221 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 自定义下拉:替换原生 <select> 的展开列表 ----------
|
||||||
|
* 用户 2026-11-04:「所有下拉框拉下来之后怎么全是灰色的?不好看,重新设计。」
|
||||||
|
*
|
||||||
|
* 🔴 为什么必须自绘:原生 <select> 的展开列表是**浏览器渲染的原生控件**,
|
||||||
|
* CSS 只能改 <option> 的 background/color,改不了圆角、行高、hover 高亮、
|
||||||
|
* 分组间距 —— 而且各平台样式完全不同(macOS 是圆角浮层、Windows 是灰方框)。
|
||||||
|
* 想让"展开后"好看,只能自己画一个列表。
|
||||||
|
*
|
||||||
|
* 设计取舍:
|
||||||
|
* - **保留原生 select 作为数据源**:不重写 HTML(全站 16 个 select,
|
||||||
|
* 逐个改容易漏、也容易破坏 onchange 内联逻辑)。
|
||||||
|
* 把原生 select 视觉隐藏,在旁边放一个按钮 + 浮层列表。
|
||||||
|
* - 原生 select 仍参与表单提交(值同步),onchange 也照样触发
|
||||||
|
* —— 调用 this.form.submit() 的那些筛选下拉因此不用改动。
|
||||||
|
* - 无 JS 时原生 select 正常可用(只是样式朴素),不会白屏。
|
||||||
|
* - 浮层用 position:fixed 定位,避免被 overflow:hidden 容器裁掉(踩过)。
|
||||||
|
*/
|
||||||
|
function bindSelect() {
|
||||||
|
var sels = document.querySelectorAll('select');
|
||||||
|
for (var i = 0; i < sels.length; i++) {
|
||||||
|
(function (sel) {
|
||||||
|
// 已经增强过、或明确排除的跳过
|
||||||
|
if (sel.getAttribute('data-enhanced') === '1') { return; }
|
||||||
|
if (sel.hasAttribute('data-no-enhance')) { return; }
|
||||||
|
// 隐藏域 / 多选 / 尺寸>1 的列表框不动
|
||||||
|
if (sel.multiple || (parseInt(sel.getAttribute('size'), 10) || 0) > 1) { return; }
|
||||||
|
|
||||||
|
sel.setAttribute('data-enhanced', '1');
|
||||||
|
|
||||||
|
// 原生 select:保留在 DOM 里(提交用),但视觉隐藏
|
||||||
|
sel.classList.add('sel-native');
|
||||||
|
|
||||||
|
// 包一层,方便定位
|
||||||
|
var wrap = document.createElement('span');
|
||||||
|
wrap.className = 'sel';
|
||||||
|
sel.parentNode.insertBefore(wrap, sel);
|
||||||
|
wrap.appendChild(sel);
|
||||||
|
|
||||||
|
// 触发按钮
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'sel-btn';
|
||||||
|
btn.setAttribute('aria-haspopup', 'listbox');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
wrap.appendChild(btn);
|
||||||
|
|
||||||
|
// 浮层列表(挂到 body,避开一切 overflow 裁剪)
|
||||||
|
var panel = document.createElement('div');
|
||||||
|
panel.className = 'sel-panel';
|
||||||
|
panel.hidden = true;
|
||||||
|
panel.setAttribute('role', 'listbox');
|
||||||
|
document.body.appendChild(panel);
|
||||||
|
|
||||||
|
var active = -1; // 键盘高亮项
|
||||||
|
|
||||||
|
function curText() {
|
||||||
|
var o = sel.options[sel.selectedIndex];
|
||||||
|
return o ? o.textContent : '';
|
||||||
|
}
|
||||||
|
function syncBtn() {
|
||||||
|
btn.innerHTML = '<span class="sel-txt"></span><span class="sel-caret" aria-hidden="true"></span>';
|
||||||
|
btn.querySelector('.sel-txt').textContent = curText();
|
||||||
|
btn.disabled = sel.disabled;
|
||||||
|
btn.classList.toggle('is-placeholder', sel.selectedIndex <= 0 && sel.options.length > 0
|
||||||
|
&& (sel.options[0].value === ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildList() {
|
||||||
|
panel.innerHTML = '';
|
||||||
|
for (var k = 0; k < sel.options.length; k++) {
|
||||||
|
(function (idx) {
|
||||||
|
var o = sel.options[idx];
|
||||||
|
if (o.hidden) { return; }
|
||||||
|
var item = document.createElement('button');
|
||||||
|
item.type = 'button';
|
||||||
|
item.className = 'sel-item';
|
||||||
|
item.setAttribute('role', 'option');
|
||||||
|
var on = idx === sel.selectedIndex;
|
||||||
|
if (on) { item.classList.add('on'); item.setAttribute('aria-selected', 'true'); }
|
||||||
|
var label = document.createElement('span');
|
||||||
|
label.className = 'sel-item-txt';
|
||||||
|
label.textContent = o.textContent;
|
||||||
|
item.appendChild(label);
|
||||||
|
if (on) {
|
||||||
|
var ck = document.createElement('span');
|
||||||
|
ck.className = 'sel-item-ck';
|
||||||
|
ck.setAttribute('aria-hidden', 'true');
|
||||||
|
item.appendChild(ck);
|
||||||
|
}
|
||||||
|
item.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
pick(idx);
|
||||||
|
});
|
||||||
|
panel.appendChild(item);
|
||||||
|
})(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pick(idx) {
|
||||||
|
if (idx < 0 || idx >= sel.options.length) { return; }
|
||||||
|
var changed = sel.selectedIndex !== idx;
|
||||||
|
sel.selectedIndex = idx;
|
||||||
|
syncBtn();
|
||||||
|
close();
|
||||||
|
// 触发原生 change,兼容那些 onchange="this.form.submit()" 的下拉
|
||||||
|
if (changed) {
|
||||||
|
var ev = document.createEvent('HTMLEvents');
|
||||||
|
ev.initEvent('change', true, false);
|
||||||
|
sel.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (btn.disabled) { return; }
|
||||||
|
buildList();
|
||||||
|
panel.hidden = false;
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
btn.classList.add('is-open');
|
||||||
|
position();
|
||||||
|
// 打开后键盘可直接上下选
|
||||||
|
active = sel.selectedIndex;
|
||||||
|
highlight(active);
|
||||||
|
document.addEventListener('mousedown', outside, true);
|
||||||
|
document.addEventListener('keydown', onKey, true);
|
||||||
|
window.addEventListener('scroll', position, true);
|
||||||
|
window.addEventListener('resize', position);
|
||||||
|
}
|
||||||
|
function close() {
|
||||||
|
if (panel.hidden) { return; }
|
||||||
|
panel.hidden = true;
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
btn.classList.remove('is-open');
|
||||||
|
document.removeEventListener('mousedown', outside, true);
|
||||||
|
document.removeEventListener('keydown', onKey, true);
|
||||||
|
window.removeEventListener('scroll', position, true);
|
||||||
|
window.removeEventListener('resize', position);
|
||||||
|
}
|
||||||
|
function outside(e) {
|
||||||
|
if (!panel.contains(e.target) && !btn.contains(e.target)) { close(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定位:fixed,挂在 body 上,不服从任何祖先的 overflow。
|
||||||
|
* 优先向下展开;下方空间不足且上方更宽裕时向上翻。 */
|
||||||
|
function position() {
|
||||||
|
if (panel.hidden) { return; }
|
||||||
|
var r = btn.getBoundingClientRect();
|
||||||
|
var vw = window.innerWidth, vh = window.innerHeight;
|
||||||
|
var pw = Math.max(panel.offsetWidth, r.width);
|
||||||
|
var ph = panel.offsetHeight;
|
||||||
|
var margin = 8;
|
||||||
|
|
||||||
|
var left = r.left;
|
||||||
|
if (left + pw + margin > vw) { left = Math.max(margin, vw - pw - margin); }
|
||||||
|
|
||||||
|
var below = vh - r.bottom - margin;
|
||||||
|
var above = r.top - margin;
|
||||||
|
var openUp = (ph > below) && (above > below);
|
||||||
|
var top = openUp ? (r.top - ph - 4) : (r.bottom + 4);
|
||||||
|
if (top < margin) { top = margin; }
|
||||||
|
if (top + ph > vh - margin) { top = Math.max(margin, vh - ph - margin); }
|
||||||
|
|
||||||
|
panel.style.left = Math.round(left) + 'px';
|
||||||
|
panel.style.top = Math.round(top) + 'px';
|
||||||
|
panel.style.minWidth = Math.round(r.width) + 'px';
|
||||||
|
panel.classList.toggle('is-up', openUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlight(idx) {
|
||||||
|
var items = panel.querySelectorAll('.sel-item');
|
||||||
|
for (var k = 0; k < items.length; k++) {
|
||||||
|
items[k].classList.toggle('is-active', k === idx);
|
||||||
|
}
|
||||||
|
if (idx >= 0 && items[idx]) {
|
||||||
|
var it = items[idx];
|
||||||
|
var t = it.offsetTop, b = t + it.offsetHeight;
|
||||||
|
if (t < panel.scrollTop) { panel.scrollTop = t; }
|
||||||
|
else if (b > panel.scrollTop + panel.clientHeight) {
|
||||||
|
panel.scrollTop = b - panel.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKey(e) {
|
||||||
|
var items = panel.querySelectorAll('.sel-item');
|
||||||
|
if (e.key === 'Escape') { e.preventDefault(); close(); btn.focus(); return; }
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
active = Math.min(items.length - 1, active + 1); highlight(active);
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
active = Math.max(0, active - 1); highlight(active);
|
||||||
|
} else if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (active >= 0) { pick(active); }
|
||||||
|
} else if (e.key === 'Home') {
|
||||||
|
e.preventDefault(); active = 0; highlight(active);
|
||||||
|
} else if (e.key === 'End') {
|
||||||
|
e.preventDefault(); active = items.length - 1; highlight(active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault(); e.stopPropagation();
|
||||||
|
if (panel.hidden) { open(); } else { close(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 原生 select 若在别处被程序改值,按钮文字要跟着变
|
||||||
|
sel.addEventListener('change', syncBtn);
|
||||||
|
|
||||||
|
syncBtn();
|
||||||
|
})(sels[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- 顶栏软件下拉菜单(右上角三横线) ----------
|
/* ---------- 顶栏软件下拉菜单(右上角三横线) ----------
|
||||||
* 极简:点了就展开一个小浮窗,点外面 / Esc 就关。
|
* 极简:点了就展开一个小浮窗,点外面 / Esc 就关。
|
||||||
* 不做遮罩、不锁背景滚动 —— 那套是侧边栏的做法,下拉菜单不需要。 */
|
* 不做遮罩、不锁背景滚动 —— 那套是侧边栏的做法,下拉菜单不需要。 */
|
||||||
@@ -1196,6 +1411,7 @@
|
|||||||
bindColorPicker();
|
bindColorPicker();
|
||||||
bindIconPicker();
|
bindIconPicker();
|
||||||
bindIconUpload();
|
bindIconUpload();
|
||||||
|
bindSelect();
|
||||||
bindSoftwarePanel();
|
bindSoftwarePanel();
|
||||||
bindMailTest();
|
bindMailTest();
|
||||||
bindInlineValidate(); // ★ 必须在 bindNoDoubleSubmit 之前
|
bindInlineValidate(); // ★ 必须在 bindNoDoubleSubmit 之前
|
||||||
|
|||||||
@@ -906,6 +906,111 @@ select:hover{
|
|||||||
}
|
}
|
||||||
.num-wrap input[type=number]{padding-right:44px}
|
.num-wrap input[type=number]{padding-right:44px}
|
||||||
|
|
||||||
|
/* ---------- 自定义下拉(替换原生 select 的展开列表)----------
|
||||||
|
* 用户 2026-11-04:「下拉框拉下来之后怎么全是灰色的?不好看,重新设计。」
|
||||||
|
*
|
||||||
|
* 🔴 原生 <select> 的展开列表由浏览器绘制,CSS 改不动(只能改 option 底色),
|
||||||
|
* 各平台样式还不统一。所以必须自绘。
|
||||||
|
* 但**不重写 HTML**:原生 select 保留参与提交,JS 在旁边生成按钮+浮层。
|
||||||
|
* 这样全站 16 个 select 自动获得新样式,也不会破坏 onchange 内联逻辑。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 原生 select:视觉隐藏但仍参与表单提交与事件(不能 display:none) */
|
||||||
|
.sel-native{
|
||||||
|
position:absolute !important;
|
||||||
|
width:1px !important; height:1px !important;
|
||||||
|
margin:0 !important; padding:0 !important; border:0 !important;
|
||||||
|
opacity:0 !important; pointer-events:none !important;
|
||||||
|
clip:rect(0 0 0 0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 外层:inline-flex 让它跟输入框一样参与行内布局 */
|
||||||
|
.sel{position:relative; display:inline-flex; vertical-align:middle; max-width:100%}
|
||||||
|
|
||||||
|
/* 触发按钮:外观与普通输入框完全一致(同高同圆角同边框) */
|
||||||
|
.sel-btn{
|
||||||
|
display:inline-flex; align-items:center; gap:10px;
|
||||||
|
height:var(--ctl-h); padding:0 12px;
|
||||||
|
border:var(--ctl-bd); border-radius:var(--ctl-r);
|
||||||
|
background:var(--card); color:var(--fg);
|
||||||
|
font-family:inherit; font-size:14.5px; line-height:1;
|
||||||
|
cursor:pointer; text-align:left;
|
||||||
|
box-shadow:var(--ctl-shadow);
|
||||||
|
transition:border-color .12s, box-shadow .12s;
|
||||||
|
max-width:100%; min-width:0;
|
||||||
|
}
|
||||||
|
.sel-btn:hover{border-color:#c3c9d1}
|
||||||
|
.sel-btn:focus-visible{outline:none; border-color:var(--brand); box-shadow:var(--ctl-shadow-focus)}
|
||||||
|
.sel-btn.is-open{border-color:var(--brand); box-shadow:var(--ctl-shadow-focus)}
|
||||||
|
.sel-txt{
|
||||||
|
flex:1; min-width:0;
|
||||||
|
overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
||||||
|
}
|
||||||
|
/* 未选择任何值时(如「全部状态」)文字用次要色,弱化存在感 */
|
||||||
|
.sel-btn.is-placeholder .sel-txt{color:var(--muted)}
|
||||||
|
/* 自绘箭头 */
|
||||||
|
.sel-caret{
|
||||||
|
width:11px; height:7px; flex:none;
|
||||||
|
background:currentColor; opacity:.55;
|
||||||
|
-webkit-mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1.2 5.5 5.8 10 1.2' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
|
||||||
|
mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'%3E%3Cpath d='M1 1.2 5.5 5.8 10 1.2' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
|
||||||
|
transition:transform .16s;
|
||||||
|
}
|
||||||
|
.sel-btn.is-open .sel-caret{transform:rotate(180deg); opacity:.85}
|
||||||
|
|
||||||
|
/* 浮层:fixed 定位,不被任何 overflow 容器裁切。
|
||||||
|
* 用白底 + 细边框 + 柔和阴影,而不是浏览器默认的灰块。 */
|
||||||
|
.sel-panel{
|
||||||
|
position:fixed; z-index:9998;
|
||||||
|
max-height:min(320px, calc(100vh - 32px));
|
||||||
|
overflow-y:auto; overscroll-behavior:contain;
|
||||||
|
padding:5px;
|
||||||
|
background:var(--card);
|
||||||
|
border:1px solid var(--line-strong);
|
||||||
|
border-radius:11px;
|
||||||
|
box-shadow:0 10px 30px rgba(16,24,40,.16), 0 2px 6px rgba(16,24,40,.06);
|
||||||
|
animation:selIn .12s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes selIn{ from{opacity:0; transform:translateY(-4px)} to{opacity:1; transform:none} }
|
||||||
|
.sel-panel.is-up{animation-name:selInUp}
|
||||||
|
@keyframes selInUp{ from{opacity:0; transform:translateY(4px)} to{opacity:1; transform:none} }
|
||||||
|
|
||||||
|
/* 列表项:圆角、行高一致、hover 有底色、选中项有对勾 */
|
||||||
|
.sel-item{
|
||||||
|
display:flex; align-items:center; gap:8px; width:100%;
|
||||||
|
padding:0 10px; height:34px;
|
||||||
|
border:0; border-radius:7px;
|
||||||
|
background:transparent; color:var(--fg);
|
||||||
|
font-family:inherit; font-size:14px; line-height:1;
|
||||||
|
cursor:pointer; text-align:left; white-space:nowrap;
|
||||||
|
transition:background .1s, color .1s;
|
||||||
|
-webkit-appearance:none; appearance:none;
|
||||||
|
}
|
||||||
|
.sel-item-txt{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis}
|
||||||
|
|
||||||
|
/* hover / 键盘高亮:中性浅灰,**不要用 --brand-soft**。
|
||||||
|
* 🔴 踩过:最初选中项用 var(--brand-soft),但那个变量会被软件主题色覆盖 ——
|
||||||
|
* 同步日历是砖红色,--brand-soft 就成了 #ffe8e9(粉红),
|
||||||
|
* 下拉里一片粉,很难看。下拉是**工具性控件**,不该跟着软件换色,
|
||||||
|
* 统一用中性色才耐看。 */
|
||||||
|
.sel-item:hover, .sel-item.is-active{background:#f1f3f6; color:var(--fg)}
|
||||||
|
|
||||||
|
/* 选中项:浅灰蓝底 + 深色文字 + 右侧对勾(不依赖主题色) */
|
||||||
|
.sel-item.on{background:#eef1f5; color:var(--fg); font-weight:600}
|
||||||
|
.sel-item.on:hover, .sel-item.on.is-active{background:#e6eaf0; color:var(--fg)}
|
||||||
|
.sel-item-ck{
|
||||||
|
width:13px; height:10px; flex:none;
|
||||||
|
background:currentColor;
|
||||||
|
-webkit-mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='10' viewBox='0 0 13 10'%3E%3Cpath d='M1.5 5.2 4.8 8.4 11.5 1.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
|
||||||
|
mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='10' viewBox='0 0 13 10'%3E%3Cpath d='M1.5 5.2 4.8 8.4 11.5 1.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窄屏:让下拉至少占满可用宽度,别挤成一小坨 */
|
||||||
|
@media (max-width:640px){
|
||||||
|
.sel{flex:1 1 auto; min-width:0}
|
||||||
|
.sel-btn{width:100%}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- 提交页容器 */
|
/* ---------- 提交页容器 */
|
||||||
* 🔴 原来只有 max-width:640px,**没有居中** —— 在 880px 的内容区里,
|
* 🔴 原来只有 max-width:640px,**没有居中** —— 在 880px 的内容区里,
|
||||||
* 表单贴左边、右侧空出 240px 白,看起来像"页面缺了一块"。
|
* 表单贴左边、右侧空出 240px 白,看起来像"页面缺了一块"。
|
||||||
|
|||||||
+20
-4
@@ -150,7 +150,12 @@ function fb_admin_taxonomy_action($pdo, $act, $post) {
|
|||||||
$slug = fb_sw_slugify($name, 'sw', $next);
|
$slug = fb_sw_slugify($name, 'sw', $next);
|
||||||
}
|
}
|
||||||
if (fb_software($slug)) { return ['英文标识「' . $slug . '」已被占用,换一个。', 'error']; }
|
if (fb_software($slug)) { return ['英文标识「' . $slug . '」已被占用,换一个。', 'error']; }
|
||||||
$sort = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM software')->fetchColumn();
|
/* 排序:优先用表单里填的值(新建表单现在也有这一项了),
|
||||||
|
* 没填才算「排到最后」。用 isset 判断而不是空判断 ——
|
||||||
|
* 用户明确填 0 也是合法值(排最前面),不能被当成"没填"。 */
|
||||||
|
$sort = isset($post['sort_order']) && $post['sort_order'] !== ''
|
||||||
|
? (int)$post['sort_order']
|
||||||
|
: (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM software')->fetchColumn();
|
||||||
$color = fb_color_norm(isset($post['color']) ? $post['color'] : '');
|
$color = fb_color_norm(isset($post['color']) ? $post['color'] : '');
|
||||||
$icon = fb_sw_icon_norm(isset($post['icon']) ? $post['icon'] : '');
|
$icon = fb_sw_icon_norm(isset($post['icon']) ? $post['icon'] : '');
|
||||||
$dl = fb_sw_url_norm(isset($post['download_url']) ? $post['download_url'] : '');
|
$dl = fb_sw_url_norm(isset($post['download_url']) ? $post['download_url'] : '');
|
||||||
@@ -591,11 +596,22 @@ function fb_admin_taxonomy_page($pdo, $editSw, $editCat) {
|
|||||||
echo ' </div>';
|
echo ' </div>';
|
||||||
echo ' </div>';
|
echo ' </div>';
|
||||||
echo ' </div>';
|
echo ' </div>';
|
||||||
|
/* 排序字段:新建和编辑**都要有**。
|
||||||
|
* 之前只在 if ($esw) 分支里渲染,导致「添加软件」时根本没有这一项,
|
||||||
|
* 用户以为排序被删了。新建时给个默认值 = 当前最大排序+1(排到最后)。
|
||||||
|
*
|
||||||
|
* ⚠️ 与「下载地址」不同:排序是**真实会被用到**的字段
|
||||||
|
* (前台软件列表、顶栏三横线菜单都按它排),不能删。 */
|
||||||
|
$sortVal = 0;
|
||||||
if ($esw) {
|
if ($esw) {
|
||||||
/* 排序是 1-2 位的小整数,占满整行很难看 —— 收窄成定宽。
|
$sortVal = (int)$esw['sort_order'];
|
||||||
* 用 .field-narrow 限制宽度,而不是让它跟着 .field 铺满。 */
|
} else {
|
||||||
|
$sortVal = (int)fb_db()->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM software')->fetchColumn();
|
||||||
|
}
|
||||||
echo ' <label class="field field-narrow"><span>排序<i class="hint">数字小的排前面,同级按名称排</i></span>'
|
echo ' <label class="field field-narrow"><span>排序<i class="hint">数字小的排前面,同级按名称排</i></span>'
|
||||||
. '<input type="number" name="sort_order" min="0" max="999" value="' . (int)$esw['sort_order'] . '"></label>';
|
. '<input type="number" name="sort_order" min="0" max="999" value="' . $sortVal . '"></label>';
|
||||||
|
|
||||||
|
if ($esw) {
|
||||||
echo ' <button class="btn primary" name="act" value="sw_edit" type="submit">保存</button> '
|
echo ' <button class="btn primary" name="act" value="sw_edit" type="submit">保存</button> '
|
||||||
. '<a class="btn" href="admin.php?tab=taxonomy">取消</a>';
|
. '<a class="btn" href="admin.php?tab=taxonomy">取消</a>';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ if (!defined('FB_ROOT')) {
|
|||||||
define('FB_ROOT', dirname(__DIR__));
|
define('FB_ROOT', dirname(__DIR__));
|
||||||
// 静态资源版本号:改 assets/ 下的 css/js 后把这个数字 +1,
|
// 静态资源版本号:改 assets/ 下的 css/js 后把这个数字 +1,
|
||||||
// 让浏览器与 CDN 缓存失效。全站只此一处,别再往页面里写死数字。
|
// 让浏览器与 CDN 缓存失效。全站只此一处,别再往页面里写死数字。
|
||||||
if (!defined('FB_ASSET_V')) { define('FB_ASSET_V', 47); }
|
if (!defined('FB_ASSET_V')) { define('FB_ASSET_V', 49); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user