fix web ui smoke and brand assets
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>LisgloSIPS - 聆界SIP管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+13
-1
@@ -495,7 +495,19 @@ export default function App() {
|
||||
<div className={`prototype-app ${collapsed ? 'sidebar-collapsed' : ''}`}>
|
||||
<aside className="sidebar">
|
||||
<div className="brand-block">
|
||||
<div className="brand-mark">聆</div>
|
||||
<div className="brand-expanded">
|
||||
<img
|
||||
className="brand-logo brand-logo-expanded"
|
||||
src="/brand/logo1.png"
|
||||
alt="聆界SIP管理平台"
|
||||
/>
|
||||
<span className="brand-sip-text">SIP</span>
|
||||
</div>
|
||||
<img
|
||||
className="brand-logo brand-logo-collapsed"
|
||||
src="/brand/logo2.png"
|
||||
alt="聆界SIP管理平台"
|
||||
/>
|
||||
<div className="brand-copy">
|
||||
<strong>聆界SIP管理平台</strong>
|
||||
<span>LisgloSIPS</span>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function Field({ label, hint, error, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Input(props) {
|
||||
export function Input({ children: _children, ...props }) {
|
||||
return <input className="ui-input" {...props} />;
|
||||
}
|
||||
|
||||
@@ -46,34 +46,37 @@ export function Select({ children, ...props }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Checkbox({ label, checked, ...props }) {
|
||||
export function Checkbox({ label, checked, children, onChange, ...props }) {
|
||||
const labelContent = label ?? children;
|
||||
return (
|
||||
<label className="ui-check">
|
||||
<input type="checkbox" checked={checked} {...props} />
|
||||
<input type="checkbox" checked={checked} onChange={(event) => onChange?.(event.target.checked, event)} {...props} />
|
||||
<span className="ui-check-box" aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
<span>{labelContent}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export function Radio({ label, checked, ...props }) {
|
||||
export function Radio({ label, checked, children, onChange, ...props }) {
|
||||
const labelContent = label ?? children;
|
||||
return (
|
||||
<label className="ui-check">
|
||||
<input type="radio" checked={checked} {...props} />
|
||||
<input type="radio" checked={checked} onChange={(event) => onChange?.(event.target.checked, event)} {...props} />
|
||||
<span className="ui-radio-dot" aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
<span>{labelContent}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export function Switch({ label, checked, ...props }) {
|
||||
export function Switch({ label, checked, children, onChange, ...props }) {
|
||||
const labelContent = label ?? children;
|
||||
return (
|
||||
<label className="ui-switch">
|
||||
<input type="checkbox" checked={checked} {...props} />
|
||||
<input type="checkbox" checked={checked} onChange={(event) => onChange?.(event.target.checked, event)} {...props} />
|
||||
<span className="ui-switch-track" aria-hidden="true">
|
||||
<span className="ui-switch-thumb" />
|
||||
</span>
|
||||
<span>{label}</span>
|
||||
<span>{labelContent}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -138,10 +141,11 @@ export function Progress({ value }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function Slider({ label, value, ...props }) {
|
||||
export function Slider({ label, value, children, ...props }) {
|
||||
const labelContent = label ?? children;
|
||||
return (
|
||||
<label className="ui-slider">
|
||||
<span>{label}</span>
|
||||
<span>{labelContent}</span>
|
||||
<input type="range" value={value} {...props} />
|
||||
<output>{value}</output>
|
||||
</label>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
|
||||
import { Badge, Button } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Panel, ApiNotice, ConfirmDialog, SimpleTable } from '../components/layout.jsx';
|
||||
import { formatDateTime, formatDurationText } from '../utils/formatters.js';
|
||||
import { metrics } from '../fixtures/devFixtures.js';
|
||||
|
||||
export function ActiveCallsPage({ activeCalls, activeCallsLoading, activeCallsError, refreshActiveCalls, can = () => true, onHangupActiveCall }) {
|
||||
const [busyId, setBusyId] = useState('');
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Field, Input, Select, Textarea } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, ConfirmDialog, SimpleTable } from '../components/layout.jsx';
|
||||
import { enStatus } from '../utils/formatters.js';
|
||||
import { enStatus, formatDate, zhStatus } from '../utils/formatters.js';
|
||||
import { api, explainApiError } from '../api.js';
|
||||
|
||||
const emptyBusinessPrefixForm = { prefix: '', name: '', description: '', priority: 100, status: 'ENABLED' };
|
||||
|
||||
function normalizeBusinessPrefix(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
prefix: item.prefix,
|
||||
name: item.name,
|
||||
description: item.description || '-',
|
||||
priority: item.priority ?? 100,
|
||||
status: zhStatus(item.status),
|
||||
gatewayCount: item.gatewayCount ?? 0,
|
||||
createdAt: formatDate(item.createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
export function BusinessPrefixesPage({ can = () => true }) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const [filters, setFilters] = useState({ keyword: '', status: 'all' });
|
||||
@@ -227,47 +240,3 @@ export function BusinessPrefixesPage({ can = () => true }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const numberLibraryTabs = [
|
||||
{ value: 'cities', label: '地级市字典' },
|
||||
{ value: 'phoneSegments', label: '手机号码库' },
|
||||
{ value: 'areaCodes', label: '城市区号' },
|
||||
{ value: 'carrierPrefixRules', label: '运营商号码段规则' },
|
||||
];
|
||||
|
||||
const numberLibraryImportExamples = {
|
||||
cities: [
|
||||
{ code: '340100', provinceCode: '340000', provinceName: '安徽省', cityName: '合肥市', cityLevel: 'PREFECTURE' },
|
||||
],
|
||||
phoneSegments: [
|
||||
{ segment7: '1380013', provinceName: '北京市', cityCode: '110100', cityName: '北京市', carrier: 'MOBILE' },
|
||||
],
|
||||
areaCodes: [
|
||||
{ areaCode: '0551', provinceName: '安徽省', cityCode: '340100', cityName: '合肥市' },
|
||||
],
|
||||
carrierPrefixRules: [
|
||||
{ prefix: '138', carrier: 'MOBILE', priority: 100 },
|
||||
],
|
||||
};
|
||||
|
||||
const emptyNumberLibraryRows = {
|
||||
cities: [],
|
||||
phoneSegments: [],
|
||||
areaCodes: [],
|
||||
carrierPrefixRules: [],
|
||||
};
|
||||
|
||||
const emptyNumberLibraryTotals = {
|
||||
cities: 0,
|
||||
phoneSegments: 0,
|
||||
areaCodes: 0,
|
||||
carrierPrefixRules: 0,
|
||||
};
|
||||
|
||||
function normalizeNumberLibraryList(payload, mapItem) {
|
||||
const items = Array.isArray(payload?.items) ? payload.items : [];
|
||||
return {
|
||||
rows: items.map(mapItem),
|
||||
total: payload?.total ?? items.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export function CustomerGatewaysPage({ gatewayRows: apiGatewayRows, setGatewayRo
|
||||
const strategyPolicies = strategyGateway
|
||||
? policyRows.filter((item) => item.gateway === strategyGateway).sort((first, second) => first.priority - second.priority)
|
||||
: [];
|
||||
const selectedBusinessPrefixCount = gatewayForm.businessPrefixIds.length;
|
||||
useEffect(() => {
|
||||
api.businessPrefixes({ status: 'ENABLED' })
|
||||
.then((items) => setBusinessPrefixOptions(Array.isArray(items) ? items : []))
|
||||
@@ -170,12 +171,22 @@ export function CustomerGatewaysPage({ gatewayRows: apiGatewayRows, setGatewayRo
|
||||
setPolicyRows((rows) => rows.filter((policy) => policy.gateway !== gateway.id));
|
||||
setSubmitting(false);
|
||||
};
|
||||
const openPolicyDrawer = (gatewayId) => {
|
||||
setStrategyGateway(gatewayId);
|
||||
setPolicyModalOpen(false);
|
||||
setEditingPolicy(null);
|
||||
setDeletePolicyTarget(null);
|
||||
setPolicyForm(emptyPolicyForm);
|
||||
const toggleBusinessPrefix = (prefixId, checked) => {
|
||||
setGatewayForm((current) => {
|
||||
const nextIds = checked
|
||||
? Array.from(new Set([...current.businessPrefixIds, prefixId]))
|
||||
: current.businessPrefixIds.filter((id) => id !== prefixId);
|
||||
return { ...current, businessPrefixIds: nextIds };
|
||||
});
|
||||
};
|
||||
const selectAllBusinessPrefixes = () => {
|
||||
setGatewayForm((current) => ({
|
||||
...current,
|
||||
businessPrefixIds: businessPrefixOptions.map((prefix) => prefix.id),
|
||||
}));
|
||||
};
|
||||
const clearBusinessPrefixes = () => {
|
||||
setGatewayForm((current) => ({ ...current, businessPrefixIds: [] }));
|
||||
};
|
||||
const closePolicyDrawer = () => {
|
||||
setStrategyGateway(null);
|
||||
@@ -287,10 +298,8 @@ export function CustomerGatewaysPage({ gatewayRows: apiGatewayRows, setGatewayRo
|
||||
<Panel title="客户网关列表" className="wide-panel">
|
||||
<SimpleTable rows={gatewayRows} columns={[
|
||||
{ key: 'id', label: 'ID', width: '104px', className: 'table-cell-compact' },
|
||||
{ key: 'name', label: '名称', width: '168px', className: 'table-cell-compact' },
|
||||
{ key: 'customer', label: '客户', width: '148px', className: 'table-cell-compact' },
|
||||
{ key: 'authMode', label: '认证方式' },
|
||||
{ key: 'authTarget', label: 'IP地址 / 账号名称', render: (row) => (row.authMode === 'IP' || row.authMode === '混合认证' ? (row.sourceIps || []).join(', ') || row.ipAddress : row.sipAccount) },
|
||||
{ key: 'name', label: '名称', width: '240px', className: 'table-cell-compact' },
|
||||
{ key: 'customer', label: '客户', width: '220px', className: 'table-cell-compact' },
|
||||
{ key: 'lineGroupName', label: '落地线路组' },
|
||||
{ key: 'rate', label: '客户费率', render: (row) => `${row.billingCycleSec || 60}s / ¥${Number(row.cycleRate || 0).toFixed(6)}` },
|
||||
{ key: 'callerRule', label: '主叫规则', render: (row) => row.callerMatchMode === 'PREFIXES' ? (row.callerPrefixes || []).join(', ') : '任意号码' },
|
||||
@@ -384,21 +393,29 @@ export function CustomerGatewaysPage({ gatewayRows: apiGatewayRows, setGatewayRo
|
||||
</Select>
|
||||
</Field>
|
||||
{gatewayForm.calleeMatchMode === 'BUSINESS_PREFIXES' ? (
|
||||
<div className="checkbox-grid">
|
||||
{businessPrefixOptions.map((prefix) => (
|
||||
<Checkbox
|
||||
key={prefix.id}
|
||||
checked={gatewayForm.businessPrefixIds.includes(prefix.id)}
|
||||
onChange={(checked) => setGatewayForm((current) => ({
|
||||
...current,
|
||||
businessPrefixIds: checked
|
||||
? [...current.businessPrefixIds, prefix.id]
|
||||
: current.businessPrefixIds.filter((id) => id !== prefix.id),
|
||||
}))}
|
||||
>
|
||||
{prefix.prefix} / {prefix.name}
|
||||
</Checkbox>
|
||||
))}
|
||||
<div className="prefix-picker">
|
||||
<div className="prefix-picker-head">
|
||||
<span>已选择 {selectedBusinessPrefixCount} / {businessPrefixOptions.length}</span>
|
||||
<div className="prefix-picker-actions">
|
||||
<Button type="button" size="sm" variant="outline" disabled={!businessPrefixOptions.length || selectedBusinessPrefixCount === businessPrefixOptions.length} onClick={selectAllBusinessPrefixes}>全选</Button>
|
||||
<Button type="button" size="sm" variant="ghost" disabled={!selectedBusinessPrefixCount} onClick={clearBusinessPrefixes}>清空</Button>
|
||||
</div>
|
||||
</div>
|
||||
{businessPrefixOptions.length ? (
|
||||
<div className="checkbox-grid">
|
||||
{businessPrefixOptions.map((prefix) => (
|
||||
<Checkbox
|
||||
key={prefix.id}
|
||||
checked={gatewayForm.businessPrefixIds.includes(prefix.id)}
|
||||
onChange={(checked) => toggleBusinessPrefix(prefix.id, checked)}
|
||||
>
|
||||
{prefix.prefix} / {prefix.name}
|
||||
</Checkbox>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="empty-inline">暂无可用业务前缀</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="modal-actions">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Field, Input, Select, Textarea } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, ConfirmDialog, SimpleTable, KeyValue } from '../components/layout.jsx';
|
||||
import { gateways } from '../fixtures/devFixtures.js';
|
||||
import { explainApiError } from '../api.js';
|
||||
|
||||
export function CustomersPage({ customerRows, setCustomerRows, addRechargeRecord, apiLoading, apiError, refreshApi, can = () => true, onCreateCustomer, onUpdateCustomer, onToggleCustomerStatus, onDeleteCustomer, onRechargeCustomer }) {
|
||||
|
||||
@@ -4,6 +4,50 @@ import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, SimpleTable } from '
|
||||
import { formatDate, zhStatus, carrierLabel } from '../utils/formatters.js';
|
||||
import { api, explainApiError } from '../api.js';
|
||||
|
||||
const numberLibraryTabs = [
|
||||
{ value: 'cities', label: '地级市字典' },
|
||||
{ value: 'phoneSegments', label: '手机号码库' },
|
||||
{ value: 'areaCodes', label: '城市区号' },
|
||||
{ value: 'carrierPrefixRules', label: '运营商号码段规则' },
|
||||
];
|
||||
|
||||
const numberLibraryImportExamples = {
|
||||
cities: [
|
||||
{ code: '340100', provinceCode: '340000', provinceName: '安徽省', cityName: '合肥市', cityLevel: 'PREFECTURE' },
|
||||
],
|
||||
phoneSegments: [
|
||||
{ segment7: '1380013', provinceName: '北京市', cityCode: '110100', cityName: '北京市', carrier: 'MOBILE' },
|
||||
],
|
||||
areaCodes: [
|
||||
{ areaCode: '0551', provinceName: '安徽省', cityCode: '340100', cityName: '合肥市' },
|
||||
],
|
||||
carrierPrefixRules: [
|
||||
{ prefix: '138', carrier: 'MOBILE', priority: 100 },
|
||||
],
|
||||
};
|
||||
|
||||
const emptyNumberLibraryRows = {
|
||||
cities: [],
|
||||
phoneSegments: [],
|
||||
areaCodes: [],
|
||||
carrierPrefixRules: [],
|
||||
};
|
||||
|
||||
const emptyNumberLibraryTotals = {
|
||||
cities: 0,
|
||||
phoneSegments: 0,
|
||||
areaCodes: 0,
|
||||
carrierPrefixRules: 0,
|
||||
};
|
||||
|
||||
function normalizeNumberLibraryList(payload, mapItem) {
|
||||
const items = Array.isArray(payload?.items) ? payload.items : [];
|
||||
return {
|
||||
rows: items.map(mapItem),
|
||||
total: payload?.total ?? items.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function NumberLibraryPage({ can = () => true }) {
|
||||
const [activeTab, setActiveTab] = useState('cities');
|
||||
const [rows, setRows] = useState(emptyNumberLibraryRows);
|
||||
|
||||
@@ -3,6 +3,10 @@ import { Badge, Button, Field, Input, Select } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Drawer, SimpleTable, KeyValue } from '../components/layout.jsx';
|
||||
import { operationLogRows } from '../fixtures/devFixtures.js';
|
||||
|
||||
function toneForStatus(status) {
|
||||
return status === '成功' || status === 'SUCCESS' ? 'success' : status === '失败' || status === 'FAILURE' ? 'danger' : 'neutral';
|
||||
}
|
||||
|
||||
export function OperationLogsPage({ logRows = operationLogRows, apiLoading, apiError, refreshApi }) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [moduleFilter, setModuleFilter] = useState('all');
|
||||
@@ -64,4 +68,3 @@ export function OperationLogsPage({ logRows = operationLogRows, apiLoading, apiE
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Badge, Button } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Panel, SimpleTable } from '../components/layout.jsx';
|
||||
import { customers, gateways, customerGatewayPolicies, vendorGatewayPolicies, routeGroups, routeRules } from '../fixtures/devFixtures.js';
|
||||
import { customerGatewayPolicies, vendorGatewayPolicies, routeGroups, routeRules } from '../fixtures/devFixtures.js';
|
||||
|
||||
export function RoutesPage() {
|
||||
return (
|
||||
@@ -57,4 +57,3 @@ export function RoutesPage() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,3 @@ export function SettingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const emptyUserForm = { username: '', name: '', phone: '', email: '', roleId: 'R002', status: '启用' };
|
||||
const emptyRoleForm = { name: '', description: '', status: '启用' };
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, Button, Progress } from '../components/ui.jsx';
|
||||
import { Badge, Button } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Panel, StatusBadge, SimpleTable } from '../components/layout.jsx';
|
||||
import { sipAccounts, opsItems } from '../fixtures/devFixtures.js';
|
||||
|
||||
@@ -22,4 +22,3 @@ export function SipOpsPage() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Badge, Button, Field, Input, Select } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, ConfirmDialog, Drawer, SimpleTable } from '../components/layout.jsx';
|
||||
import { formatDate, zhStatus } from '../utils/formatters.js';
|
||||
import { gateways, vendorLineGroups } from '../fixtures/devFixtures.js';
|
||||
import { explainApiError } from '../api.js';
|
||||
|
||||
@@ -183,18 +182,3 @@ export function VendorLineGroupsPage({ lineGroupRows: apiLineGroupRows, setLineG
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const emptyBusinessPrefixForm = { prefix: '', name: '', description: '', priority: 100, status: 'ENABLED' };
|
||||
|
||||
function normalizeBusinessPrefix(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
prefix: item.prefix,
|
||||
name: item.name,
|
||||
description: item.description || '-',
|
||||
priority: item.priority ?? 100,
|
||||
status: zhStatus(item.status),
|
||||
gatewayCount: item.gatewayCount ?? 0,
|
||||
createdAt: formatDate(item.createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Field, Input, Textarea } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, ConfirmDialog, SimpleTable, KeyValue } from '../components/layout.jsx';
|
||||
import { gateways } from '../fixtures/devFixtures.js';
|
||||
import { explainApiError } from '../api.js';
|
||||
|
||||
export function VendorsPage({ vendorRows, setVendorRows, addRechargeRecord, apiLoading, apiError, refreshApi, can = () => true, onCreateVendor, onUpdateVendor, onDeleteVendor, onRechargeVendor }) {
|
||||
|
||||
+117
-5
@@ -156,13 +156,48 @@ button:disabled {
|
||||
|
||||
.brand-block {
|
||||
display: grid;
|
||||
grid-template-columns: 38px minmax(0, 1fr) 34px;
|
||||
grid-template-columns: minmax(0, 1fr) 34px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 10px;
|
||||
padding: 16px 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-expanded {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.brand-logo-expanded {
|
||||
width: min(100%, 152px);
|
||||
flex: 0 1 auto;
|
||||
height: 38px;
|
||||
object-position: left center;
|
||||
}
|
||||
|
||||
.brand-sip-text {
|
||||
flex: 0 0 auto;
|
||||
color: var(--brand);
|
||||
font-size: 19px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.brand-logo-collapsed {
|
||||
display: none;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -175,7 +210,7 @@ button:disabled {
|
||||
}
|
||||
|
||||
.brand-copy {
|
||||
display: grid;
|
||||
display: none;
|
||||
min-width: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
@@ -296,6 +331,18 @@ button:disabled {
|
||||
padding: 14px 10px;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-logo-expanded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-expanded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-logo-collapsed {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-copy,
|
||||
.sidebar-collapsed .nav-group p,
|
||||
.sidebar-collapsed .nav-label,
|
||||
@@ -862,6 +909,60 @@ button:disabled {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.prefix-picker {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.prefix-picker-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.prefix-picker-actions {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 10px 14px;
|
||||
}
|
||||
|
||||
.checkbox-grid .ui-check {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.checkbox-grid .ui-check > span:last-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.empty-inline {
|
||||
padding: 12px;
|
||||
color: var(--muted);
|
||||
border: 1px dashed var(--line);
|
||||
border-radius: 6px;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.mini-chart {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
@@ -1680,12 +1781,23 @@ button:disabled {
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-block {
|
||||
grid-template-columns: 38px minmax(0, 1fr) 34px;
|
||||
grid-template-columns: minmax(0, 1fr) 34px;
|
||||
justify-items: stretch;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-copy,
|
||||
.sidebar-collapsed .brand-logo-expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-expanded {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .brand-logo-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .nav-label {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
const selectedBlue = '#2563EB';
|
||||
|
||||
function formatCurrency(value, digits = 2) {
|
||||
const numeric = Number(value || 0);
|
||||
return `¥${numeric.toLocaleString('zh-CN', { minimumFractionDigits: digits, maximumFractionDigits: digits })}`;
|
||||
|
||||
Reference in New Issue
Block a user