feat(sku): 产品速查浮层(Ctrl/⌘+J)
报价报到一半想确认某个货的采购价、起订量、箱规时用。跟已有的两个东西都 不是一件事: ⌘K 全局搜索 是「找到后跳过去」—— 一跳走,正在写的报价就断了 SkuPicker 是「挑一个塞进表单」—— 要点产品组再点货号,两层 速查 是「就地看数」—— 不跳转、不改任何东西,Esc 回到刚才的地方 · 新接口 GET /api/sku/quick:一次把采购价、起订量、供应商、规格、箱规、 阶梯价全带回来,不返回 path(它不是用来跳转的)。 · 货号精确/前缀命中排最前。报价时多半直接敲货号,让它跟在一堆品名模糊 匹配后面就等于没做速查。 · 箱规拼成「40×30×25cm / 12.5kg / 装 24」一句话 —— 报价要的是这一句, 不是四个分开的数字。 · 点货号或价格可复制;clipboard API 在非 HTTPS 内网下不可用,退回 execCommand。 · 没占 ⌘K,因为那已经是全局搜索了,抢掉会打断老习惯。 · 页脚写死「价格是采购价,不是报价」防手滑。 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f32a2daaef
commit
226949fa53
@@ -4,6 +4,7 @@ import LoginPage from './pages/LoginPage'
|
|||||||
import Settings from './components/Settings'
|
import Settings from './components/Settings'
|
||||||
import GlobalHeader from './components/GlobalHeader'
|
import GlobalHeader from './components/GlobalHeader'
|
||||||
import AIAssistant from './components/AIAssistant'
|
import AIAssistant from './components/AIAssistant'
|
||||||
|
import QuickLookup from './components/QuickLookup'
|
||||||
import { readPreference } from './preferences'
|
import { readPreference } from './preferences'
|
||||||
import { AuthProvider, useAuth, hasPermission } from './auth'
|
import { AuthProvider, useAuth, hasPermission } from './auth'
|
||||||
import { loadBranding, useBranding, setPageTitle } from './branding'
|
import { loadBranding, useBranding, setPageTitle } from './branding'
|
||||||
@@ -348,6 +349,8 @@ function Shell({ theme, onThemeChange }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<AIAssistant isOpen={assistantOpen} onClose={closeAssistant} />
|
<AIAssistant isOpen={assistantOpen} onClose={closeAssistant} />
|
||||||
|
{/* 产品速查(Ctrl/⌘+J):自己管开关,挂在外壳里就能在任何页面唤出 */}
|
||||||
|
<QuickLookup />
|
||||||
<Settings
|
<Settings
|
||||||
isOpen={settingsOpen}
|
isOpen={settingsOpen}
|
||||||
onClose={closeSettings}
|
onClose={closeSettings}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { SearchIcon, CloseIcon } from './Icons'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品速查。
|
||||||
|
*
|
||||||
|
* 跟 ⌘K 全局搜索和 SkuPicker 都不是一件事:
|
||||||
|
* · ⌘K 是「找到后跳过去」—— 一跳走,正在写的报价就断了;
|
||||||
|
* · SkuPicker 是「挑一个塞进表单」—— 要点产品组再点货号,两层;
|
||||||
|
* · 速查是「报价报到一半,想起来要确认一下这个货多少钱、起订多少、一箱装几个」。
|
||||||
|
* 所以它不跳转、不改任何东西,就是把数摆出来,还能一键复制。
|
||||||
|
*
|
||||||
|
* 快捷键 Ctrl/⌘ + J。没占 K 是因为 ⌘K 已经是全局搜索了,抢掉会把老习惯打断。
|
||||||
|
*/
|
||||||
|
|
||||||
|
const money = (value, currency) => {
|
||||||
|
if (value === null || value === undefined || value === '') return '—'
|
||||||
|
const num = Number(value)
|
||||||
|
if (!Number.isFinite(num)) return '—'
|
||||||
|
return `${currency || ''} ${num.toFixed(2)}`.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function QuickLookup() {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [keyword, setKeyword] = useState('')
|
||||||
|
const [results, setResults] = useState([])
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [copied, setCopied] = useState('')
|
||||||
|
const inputRef = useRef(null)
|
||||||
|
|
||||||
|
/* 开关:Ctrl/⌘+J 开,Esc 关。开的时候自动聚焦并全选,
|
||||||
|
连着查第二个货号时不用先手动删掉上一个。 */
|
||||||
|
useEffect(() => {
|
||||||
|
const onKeyDown = event => {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'j') {
|
||||||
|
event.preventDefault()
|
||||||
|
setOpen(true)
|
||||||
|
window.setTimeout(() => {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
inputRef.current?.select()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', onKeyDown)
|
||||||
|
return () => window.removeEventListener('keydown', onKeyDown)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
const onKeyDown = event => { if (event.key === 'Escape') setOpen(false) }
|
||||||
|
window.addEventListener('keydown', onKeyDown)
|
||||||
|
return () => window.removeEventListener('keydown', onKeyDown)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
/* 查询:180ms 防抖,跟全局搜索保持同一个手感 */
|
||||||
|
useEffect(() => {
|
||||||
|
const term = keyword.trim()
|
||||||
|
if (!term) { setResults([]); setError(''); return }
|
||||||
|
let cancelled = false
|
||||||
|
const timer = window.setTimeout(async () => {
|
||||||
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get('/api/sku/quick', { params: { q: term } })
|
||||||
|
if (!cancelled) setResults(data.results || [])
|
||||||
|
} catch (err) {
|
||||||
|
if (!cancelled) {
|
||||||
|
setResults([])
|
||||||
|
setError(err?.response?.data?.error || '查不动了,确认一下后端还在不在')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setLoading(false)
|
||||||
|
}
|
||||||
|
}, 180)
|
||||||
|
return () => { cancelled = true; window.clearTimeout(timer) }
|
||||||
|
}, [keyword])
|
||||||
|
|
||||||
|
/* 复制:报价时最常做的动作就是把货号或价格抄到别处去。
|
||||||
|
navigator.clipboard 在非 HTTPS 的内网环境下可能不可用,退回 execCommand。 */
|
||||||
|
const copy = useCallback(async (text, tag) => {
|
||||||
|
const value = String(text ?? '')
|
||||||
|
if (!value) return
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value)
|
||||||
|
} catch {
|
||||||
|
const helper = document.createElement('textarea')
|
||||||
|
helper.value = value
|
||||||
|
helper.style.position = 'fixed'
|
||||||
|
helper.style.opacity = '0'
|
||||||
|
document.body.appendChild(helper)
|
||||||
|
helper.select()
|
||||||
|
try { document.execCommand('copy') } catch { /* 复制不了就算了,不弹错 */ }
|
||||||
|
helper.remove()
|
||||||
|
}
|
||||||
|
setCopied(tag)
|
||||||
|
window.setTimeout(() => setCopied(current => (current === tag ? '' : current)), 1200)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!open) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ql-backdrop" onMouseDown={event => { if (event.target === event.currentTarget) setOpen(false) }}>
|
||||||
|
<div className="ql-panel" role="dialog" aria-label="产品速查">
|
||||||
|
<div className="ql-head">
|
||||||
|
<SearchIcon size={17} />
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={keyword}
|
||||||
|
onChange={event => setKeyword(event.target.value)}
|
||||||
|
placeholder="货号 / 品名 / 规格 —— 报价时查采购价与起订量"
|
||||||
|
autoComplete="off"
|
||||||
|
aria-label="产品速查关键词"
|
||||||
|
/>
|
||||||
|
<kbd>Esc</kbd>
|
||||||
|
<button type="button" className="ql-close" onClick={() => setOpen(false)} aria-label="关闭"><CloseIcon size={15} /></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ql-body">
|
||||||
|
{!keyword.trim() && (
|
||||||
|
<div className="ql-hint">
|
||||||
|
敲货号直接命中;敲品名按采购价从低到高排。<br />
|
||||||
|
这里<strong>只看不改</strong>,随时 Esc 回到刚才的地方。
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{loading && <div className="ql-hint">正在查…</div>}
|
||||||
|
{error && <div className="ql-hint ql-error">{error}</div>}
|
||||||
|
{!loading && !error && keyword.trim() && !results.length && (
|
||||||
|
<div className="ql-hint">没找到。换个关键词,或者确认这个货号的合同导入了没。</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{results.map(item => (
|
||||||
|
<div className="ql-row" key={item.sku}>
|
||||||
|
<div className="ql-main">
|
||||||
|
<div className="ql-sku">
|
||||||
|
<button type="button" className="ql-copy-sku" title="复制货号" onClick={() => copy(item.displaySku, `sku-${item.sku}`)}>
|
||||||
|
{item.displaySku}
|
||||||
|
{copied === `sku-${item.sku}` && <span className="ql-copied">已复制</span>}
|
||||||
|
</button>
|
||||||
|
{item.confidence === 'low' && <span className="ql-flag">待核对</span>}
|
||||||
|
</div>
|
||||||
|
<div className="ql-name">{item.name || '—'}</div>
|
||||||
|
{item.nameEn && <div className="ql-sub">{item.nameEn}</div>}
|
||||||
|
{item.spec && <div className="ql-sub">规格 {item.spec}</div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ql-numbers">
|
||||||
|
<button type="button" className="ql-price" title="复制价格" onClick={() => copy(item.price ?? '', `price-${item.sku}`)}>
|
||||||
|
{money(item.price, item.currency)}
|
||||||
|
{copied === `price-${item.sku}` && <span className="ql-copied">已复制</span>}
|
||||||
|
</button>
|
||||||
|
<div className="ql-meta">
|
||||||
|
<span>起订 {item.moq ?? '—'}</span>
|
||||||
|
<span>{item.supplier || '无供应商'}</span>
|
||||||
|
</div>
|
||||||
|
{item.packing && (
|
||||||
|
<div className="ql-meta ql-packing">
|
||||||
|
{item.packing.cartonQty ? <span>装箱 {item.packing.cartonQty}</span> : null}
|
||||||
|
{item.packing.cartonSize ? <span>{item.packing.cartonSize}</span> : null}
|
||||||
|
{item.packing.cartonWeight ? <span>{item.packing.cartonWeight}kg</span> : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!item.tiers?.length && (
|
||||||
|
<div className="ql-tiers">
|
||||||
|
{item.tiers.map((tier, index) => (
|
||||||
|
<span key={index}>{tier.moq_from}{tier.moq_to ? `-${tier.moq_to}` : '+'} → {money(tier.price, tier.currency)}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!results.length && (
|
||||||
|
<div className="ql-foot">
|
||||||
|
{results.length} 条 · 点货号或价格可复制 · 价格是<strong>采购价</strong>,不是报价
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuickLookup
|
||||||
@@ -1510,3 +1510,215 @@ body { -webkit-font-smoothing: auto; }
|
|||||||
.cleanup-toolbar { justify-content: flex-start; }
|
.cleanup-toolbar { justify-content: flex-start; }
|
||||||
.cleanup-toolbar-right { width: 100%; }
|
.cleanup-toolbar-right { width: 100%; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- 产品速查(Ctrl/⌘+J) ----------------------------
|
||||||
|
报价报到一半想确认一下某个货的采购价/起订量/箱规时用。只读、不跳转,
|
||||||
|
Esc 就回到刚才的地方 —— 所以它是浮层而不是一个页面。 */
|
||||||
|
|
||||||
|
.ql-backdrop {
|
||||||
|
position: fixed; inset: 0; z-index: 1200;
|
||||||
|
display: flex; align-items: flex-start; justify-content: center;
|
||||||
|
padding: 10vh 16px 24px;
|
||||||
|
background: rgba(6, 12, 20, 0.55);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
.ql-panel {
|
||||||
|
width: min(760px, 100%);
|
||||||
|
max-height: 76vh;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: var(--radius-lg, 12px);
|
||||||
|
background: var(--surface-base, #fff);
|
||||||
|
box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-head {
|
||||||
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
padding: 13px 14px;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
background: var(--surface-deep);
|
||||||
|
}
|
||||||
|
.ql-head svg { flex: none; color: var(--text-tertiary); }
|
||||||
|
/* 16px 是底线:再小 iOS 聚焦时会把整页放大 */
|
||||||
|
.ql-head input {
|
||||||
|
flex: 1; min-width: 0;
|
||||||
|
border: none; background: transparent; outline: none;
|
||||||
|
color: var(--text-primary); font: inherit; font-size: 16px;
|
||||||
|
}
|
||||||
|
.ql-head kbd {
|
||||||
|
flex: none; padding: 2px 7px;
|
||||||
|
border: 1px solid var(--border-light); border-radius: 5px;
|
||||||
|
color: var(--text-tertiary); font-size: 11.5px; font-family: inherit;
|
||||||
|
}
|
||||||
|
.ql-close {
|
||||||
|
flex: none; display: inline-flex; padding: 4px;
|
||||||
|
border: none; background: transparent; color: var(--text-tertiary); cursor: pointer;
|
||||||
|
}
|
||||||
|
.ql-close:hover { color: var(--text-primary); }
|
||||||
|
|
||||||
|
.ql-body { overflow-y: auto; padding: 6px; }
|
||||||
|
|
||||||
|
.ql-hint {
|
||||||
|
padding: 22px 16px; text-align: center;
|
||||||
|
color: var(--text-tertiary); font-size: 13.5px; line-height: 1.8;
|
||||||
|
}
|
||||||
|
.ql-error { color: var(--danger, #c0392b); }
|
||||||
|
|
||||||
|
.ql-row {
|
||||||
|
display: flex; align-items: flex-start; gap: 14px;
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-radius: var(--radius-md, 8px);
|
||||||
|
}
|
||||||
|
.ql-row:hover { background: var(--surface-hover); }
|
||||||
|
.ql-row + .ql-row { border-top: 1px solid var(--border-light); }
|
||||||
|
|
||||||
|
.ql-main { flex: 1; min-width: 0; }
|
||||||
|
.ql-sku { display: flex; align-items: center; gap: 7px; }
|
||||||
|
/* 货号和价格都做成按钮:报价时最常做的动作就是把它们抄到别处 */
|
||||||
|
.ql-copy-sku {
|
||||||
|
position: relative;
|
||||||
|
border: none; background: transparent; padding: 0; cursor: pointer;
|
||||||
|
color: var(--text-primary); font: inherit;
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 14.5px; font-weight: 600;
|
||||||
|
}
|
||||||
|
.ql-copy-sku:hover { color: var(--primary, #2f6fed); text-decoration: underline; }
|
||||||
|
.ql-flag {
|
||||||
|
padding: 1px 6px; border-radius: 4px;
|
||||||
|
background: var(--warning-bg, #fdf3e3); color: var(--warning, #a9730d);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.ql-name { margin-top: 3px; color: var(--text-primary); font-size: 13.5px; }
|
||||||
|
.ql-sub { margin-top: 2px; color: var(--text-tertiary); font-size: 12.5px; }
|
||||||
|
|
||||||
|
.ql-numbers { flex: none; width: 236px; text-align: right; }
|
||||||
|
.ql-price {
|
||||||
|
position: relative;
|
||||||
|
border: none; background: transparent; padding: 0; cursor: pointer;
|
||||||
|
color: var(--primary, #2f6fed); font: inherit;
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 16px; font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.ql-price:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
.ql-meta {
|
||||||
|
display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;
|
||||||
|
margin-top: 3px; color: var(--text-tertiary); font-size: 12.5px;
|
||||||
|
}
|
||||||
|
.ql-packing { color: var(--text-secondary); }
|
||||||
|
.ql-tiers {
|
||||||
|
display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;
|
||||||
|
margin-top: 4px; font-size: 12px;
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 复制成功的角标:贴在被点的那个数字右上角,不挤动布局 */
|
||||||
|
.ql-copied {
|
||||||
|
position: absolute; left: 100%; top: -2px; margin-left: 6px;
|
||||||
|
padding: 1px 6px; border-radius: 4px; white-space: nowrap;
|
||||||
|
background: var(--success-bg, #e6f4ea); color: var(--success, #1e7e34);
|
||||||
|
font-size: 11px; font-weight: 400; font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-foot {
|
||||||
|
padding: 9px 14px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
background: var(--surface-deep);
|
||||||
|
color: var(--text-tertiary); font-size: 12.5px; text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.ql-backdrop { padding: 6vh 10px 16px; }
|
||||||
|
.ql-row { flex-wrap: wrap; gap: 6px; }
|
||||||
|
.ql-numbers { width: 100%; text-align: left; }
|
||||||
|
.ql-meta, .ql-tiers { justify-content: flex-start; }
|
||||||
|
.ql-copied { position: static; margin-left: 6px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- 订单支出抽屉 ---------------------------- */
|
||||||
|
|
||||||
|
.oe-hint { padding: 20px 0; color: var(--text-tertiary); font-size: 13.5px; }
|
||||||
|
.oe-error {
|
||||||
|
margin: 0 0 12px; padding: 9px 12px;
|
||||||
|
border: 1px solid var(--danger-border, var(--border-light)); border-radius: var(--radius-md, 8px);
|
||||||
|
background: var(--danger-bg, #fdecea); color: var(--danger, #c0392b); font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oe-summary { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||||
|
.oe-stat {
|
||||||
|
flex: 1; min-width: 150px;
|
||||||
|
padding: 11px 13px;
|
||||||
|
border: 1px solid var(--border-light); border-radius: var(--radius-md, 8px);
|
||||||
|
background: var(--surface-deep);
|
||||||
|
}
|
||||||
|
.oe-stat span { display: block; color: var(--text-tertiary); font-size: 12.5px; }
|
||||||
|
.oe-stat strong {
|
||||||
|
display: block; margin-top: 3px;
|
||||||
|
font-family: var(--font-mono, ui-monospace, monospace);
|
||||||
|
font-size: 18px; font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.oe-stat .oe-unpaid { color: var(--warning, #a9730d); }
|
||||||
|
/* 这一行必须留着:减掉的只是杂费,没减货款采购成本。
|
||||||
|
不写清楚会被当成真毛利拿去报价。 */
|
||||||
|
.oe-stat em { display: block; margin-top: 2px; color: var(--text-tertiary); font-size: 11.5px; font-style: normal; }
|
||||||
|
|
||||||
|
.oe-bycat { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
|
||||||
|
.oe-bycat span {
|
||||||
|
padding: 3px 9px; border: 1px solid var(--border-light); border-radius: 20px;
|
||||||
|
background: var(--surface-base); color: var(--text-secondary); font-size: 12.5px;
|
||||||
|
}
|
||||||
|
.oe-bycat b { font-family: var(--font-mono, ui-monospace, monospace); }
|
||||||
|
|
||||||
|
.oe-form {
|
||||||
|
margin-bottom: 16px; padding: 13px;
|
||||||
|
border: 1px solid var(--border-light); border-radius: var(--radius-md, 8px);
|
||||||
|
background: var(--surface-deep);
|
||||||
|
}
|
||||||
|
.oe-form-title { margin-bottom: 10px; font-size: 13.5px; font-weight: 600; color: var(--text-primary); }
|
||||||
|
.oe-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(146px, 1fr)); gap: 9px 10px; }
|
||||||
|
.oe-grid label { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: var(--text-tertiary); }
|
||||||
|
.oe-grid input, .oe-grid select {
|
||||||
|
padding: 7px 9px;
|
||||||
|
border: 1px solid var(--border-light); border-radius: var(--radius-sm, 6px);
|
||||||
|
background: var(--surface-base); color: var(--text-primary);
|
||||||
|
font: inherit; font-size: 13.5px;
|
||||||
|
}
|
||||||
|
.oe-grid .oe-wide { grid-column: 1 / -1; }
|
||||||
|
/* 跨币种时才出现的汇率格,给个底色提示这是必填的 */
|
||||||
|
.oe-grid .oe-rate input { border-color: var(--primary-border, var(--border-light)); background: var(--primary-bg, var(--surface-base)); }
|
||||||
|
|
||||||
|
.oe-form-foot { display: flex; align-items: center; gap: 9px; margin-top: 11px; }
|
||||||
|
.oe-grow { flex: 1; }
|
||||||
|
.oe-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); cursor: pointer; }
|
||||||
|
|
||||||
|
.oe-table { margin-top: 4px; }
|
||||||
|
.oe-table .oe-num { text-align: right; font-family: var(--font-mono, ui-monospace, monospace); font-variant-numeric: tabular-nums; }
|
||||||
|
.oe-table tr.oe-editing > td { background: var(--primary-bg, var(--surface-hover)); }
|
||||||
|
.oe-voucher { margin-top: 2px; color: var(--text-tertiary); font-size: 12px; font-family: var(--font-mono, ui-monospace, monospace); }
|
||||||
|
.oe-note { margin-top: 2px; color: var(--text-tertiary); font-size: 12px; }
|
||||||
|
.oe-ops { white-space: nowrap; }
|
||||||
|
.oe-del { color: var(--danger, #c0392b); }
|
||||||
|
|
||||||
|
.oe-paid {
|
||||||
|
padding: 2px 9px; border-radius: 20px; cursor: pointer;
|
||||||
|
border: 1px solid var(--warning-border, var(--border-light));
|
||||||
|
background: var(--warning-bg, #fdf3e3); color: var(--warning, #a9730d);
|
||||||
|
font: inherit; font-size: 12px;
|
||||||
|
}
|
||||||
|
.oe-paid.is-paid {
|
||||||
|
border-color: var(--success-border, var(--border-light));
|
||||||
|
background: var(--success-bg, #e6f4ea); color: var(--success, #1e7e34);
|
||||||
|
}
|
||||||
|
.oe-paid:disabled { cursor: default; }
|
||||||
|
|
||||||
|
/* 订单列表里的杂费列 */
|
||||||
|
.order-expense-unpaid { color: var(--warning, #a9730d); font-style: normal; font-size: 12px; }
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.oe-summary { flex-direction: column; }
|
||||||
|
.oe-grid { grid-template-columns: 1fr 1fr; }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user