Update App.jsx via upload script - 2026-08-01 00:10:02

This commit is contained in:
mike 2026-08-01 00:10:26 +08:00
parent 2c865f76ba
commit 05f474ee44
1 changed files with 4 additions and 73 deletions

77
App.jsx
View File

@ -13,7 +13,7 @@ import {
getAiConfig, saveAiConfig, deleteAiConfig, testAiConfig, generateAiDraft,
parseCatalogFiles, translateTerms,
getTelegramConfig, saveTelegramConfig, deleteTelegramConfig, testTelegramConfig,
checkJmsLine, fetchJmsTraffic,
checkJmsLine,
} from './storage';
/* ----------------------------- constants ----------------------------- */
@ -248,7 +248,7 @@ const COUNTRIES = [
const COUNTRY_BY_NAME = Object.fromEntries(COUNTRIES.map(c => [c.name, c]));
const STORAGE_KEYS = { customers: 'crm:customers', products: 'crm:products', settings: 'crm:settings', quotes: 'crm:quotes', sellerProfiles: 'crm:sellerProfiles', certifications: 'crm:certifications', productCategories: 'crm:productCategories', packingLists: 'crm:packingLists', catalogDocs: 'crm:catalogDocs', jmsLines: 'crm:jmsLines', jmsSubUrl: 'crm:jmsSubUrl' };
const STORAGE_KEYS = { customers: 'crm:customers', products: 'crm:products', settings: 'crm:settings', quotes: 'crm:quotes', sellerProfiles: 'crm:sellerProfiles', certifications: 'crm:certifications', productCategories: 'crm:productCategories', packingLists: 'crm:packingLists', catalogDocs: 'crm:catalogDocs', jmsLines: 'crm:jmsLines' };
const blankCustomer = () => ({
id: 'c_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
@ -1266,16 +1266,11 @@ async function loadAll() {
if (cd && cd.value) catalogDocs = JSON.parse(cd.value);
} catch (e) {}
let jmsLines = [];
let jmsSubUrl = '';
try {
const jl = await storageGet(STORAGE_KEYS.jmsLines);
if (jl && jl.value) jmsLines = JSON.parse(jl.value);
} catch (e) {}
try {
const ju = await storageGet(STORAGE_KEYS.jmsSubUrl);
if (ju && ju.value) jmsSubUrl = ju.value;
} catch (e) {}
return { customers, products, settings, quotes, sellerProfiles, certifications, productCategories, packingLists, catalogDocs, jmsLines, jmsSubUrl };
return { customers, products, settings, quotes, sellerProfiles, certifications, productCategories, packingLists, catalogDocs, jmsLines };
}
/* -------------------------------- styles -------------------------------- */
@ -1413,21 +1408,6 @@ const Styles = () => (
.stat-num { font-family: 'Space Grotesk', sans-serif; font-size: 22px; font-weight: 700; }
.stat-lbl { font-size: 11.5px; color: var(--ink-soft); }
.jms-traffic-widget { position: relative; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 16px; min-width: 252px; display: flex; flex-direction: column; gap: 6px; justify-content: center; }
.jms-traffic-top { display: flex; align-items: center; gap: 6px; }
.jms-traffic-title { font-size: 11.5px; font-weight: 700; color: var(--ink-soft); }
.jms-traffic-badge { font-size: 9.5px; font-weight: 600; color: var(--copper); background: var(--copper-soft); padding: 2px 7px; border-radius: 100px; }
.jms-icon-btn { margin-left: 0; background: none; border: none; color: var(--ink-soft); cursor: pointer; padding: 2px; display: flex; border-radius: 4px; }
.jms-icon-btn:last-child { margin-left: auto; }
.jms-icon-btn:hover { background: var(--bg); color: var(--ink); }
.jms-traffic-bar { height: 6px; background: var(--line); border-radius: 100px; overflow: hidden; }
.jms-traffic-bar-fill { height: 100%; background: linear-gradient(90deg, var(--copper), #D08A44); border-radius: 100px; transition: width .4s; }
.jms-traffic-meta { font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 600; color: var(--ink); }
.jms-traffic-settings {
position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; background: var(--panel);
border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); width: 260px;
}
.jms-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
.jms-card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 16px 16px 14px; display: flex; flex-direction: column; gap: 12px; }
.jms-card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
@ -1965,9 +1945,6 @@ function MainApp({ username, onLogout }) {
const [catalogEditDraft, setCatalogEditDraft] = useState({ title: '', category: '', brand: '', type: '' });
const [catalogCategoryFilter, setCatalogCategoryFilter] = useState('all');
const [jmsLines, setJmsLines] = useState([]);
const [jmsSubUrl, setJmsSubUrl] = useState('');
const [jmsTraffic, setJmsTraffic] = useState(null); // { upload, download, total, expire } null
const [jmsTrafficLoading, setJmsTrafficLoading] = useState(false);
const [telegramConfigHasToken, setTelegramConfigHasToken] = useState(false);
const [telegramConfigChatId, setTelegramConfigChatId] = useState('');
@ -1994,7 +1971,6 @@ function MainApp({ username, onLogout }) {
setPackingLists(data.packingLists);
setCatalogDocs((data.catalogDocs || []).filter(d => d.fileName));
setJmsLines(data.jmsLines || []);
setJmsSubUrl(data.jmsSubUrl || '');
setLoaded(true);
})();
}, []);
@ -2014,7 +1990,6 @@ function MainApp({ username, onLogout }) {
useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.packingLists, JSON.stringify(packingLists)).catch(() => {}); }, [packingLists, loaded]);
useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.catalogDocs, JSON.stringify(catalogDocs)).catch(() => {}); }, [catalogDocs, loaded]);
useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.jmsLines, JSON.stringify(jmsLines)).catch(() => {}); }, [jmsLines, loaded]);
useEffect(() => { if (loaded) storageSet(STORAGE_KEYS.jmsSubUrl, jmsSubUrl).catch(() => {}); }, [jmsSubUrl, loaded]);
function notify(msg) { setToast(msg); setTimeout(() => setToast(''), 2200); }
@ -2424,20 +2399,6 @@ function MainApp({ username, onLogout }) {
}
}
async function refreshJmsTraffic() {
if (!jmsSubUrl.trim()) { notify('请先在流量小组件的设置里填写标准订阅地址'); return; }
setJmsTrafficLoading(true);
try {
const res = await fetchJmsTraffic(jmsSubUrl.trim());
if (res.ok) setJmsTraffic(res);
else notify(res.error || '获取流量信息失败');
} catch (e) {
notify('获取流量信息失败:' + e.message);
} finally {
setJmsTrafficLoading(false);
}
}
async function saveTelegramSettings(botToken, chatId) {
try {
await saveTelegramConfig(botToken, chatId);
@ -3180,11 +3141,6 @@ function MainApp({ username, onLogout }) {
onSave={saveJmsLine}
onDelete={deleteJmsLine}
onCheck={checkJmsLineNow}
subUrl={jmsSubUrl}
onSubUrlChange={setJmsSubUrl}
traffic={jmsTraffic}
trafficLoading={jmsTrafficLoading}
onRefreshTraffic={refreshJmsTraffic}
onNotify={notify}
/>
)}
@ -4420,7 +4376,7 @@ function JmsLineFormFields({ group, protocol, draft, setDraft }) {
return null;
}
function JmsPage({ lines, onSave, onDelete, onCheck, subUrl, onSubUrlChange, traffic, trafficLoading, onRefreshTraffic, onNotify }) {
function JmsPage({ lines, onSave, onDelete, onCheck, onNotify }) {
const [ipMap, setIpMap] = useState({});
const [modalOpen, setModalOpen] = useState(false);
const [editingId, setEditingId] = useState(null);
@ -4429,7 +4385,6 @@ function JmsPage({ lines, onSave, onDelete, onCheck, subUrl, onSubUrlChange, tra
const [draft, setDraft] = useState({});
const [qrLine, setQrLine] = useState(null);
const [qrDataUrl, setQrDataUrl] = useState('');
const [subSettingsOpen, setSubSettingsOpen] = useState(false);
const total = lines.length;
const ssCount = lines.filter(l => l.protocol === 'ss').length;
@ -4513,30 +4468,6 @@ function JmsPage({ lines, onSave, onDelete, onCheck, subUrl, onSubUrlChange, tra
<div className="stat-chip"><div className="stat-num">{ssCount}</div><div className="stat-lbl">SS</div></div>
<div className="stat-chip"><div className="stat-num">{vmessCount}</div><div className="stat-lbl">VMess</div></div>
<div className="stat-chip"><div className="stat-num">{vlessCount}</div><div className="stat-lbl">VLESS</div></div>
<div className="jms-traffic-widget">
<div className="jms-traffic-top">
<span className="jms-traffic-title">JMS 流量</span>
<span className="jms-traffic-badge">{traffic ? '已同步' : '未同步'}</span>
<button className="jms-icon-btn" title="刷新用量" onClick={onRefreshTraffic} disabled={trafficLoading}>
<RefreshCw size={12} className={trafficLoading ? 'jms-spin' : ''} />
</button>
<button className="jms-icon-btn" title="订阅地址设置" onClick={() => setSubSettingsOpen(v => !v)}><Settings2 size={12} /></button>
</div>
<div className="jms-traffic-bar"><div className="jms-traffic-bar-fill" style={{ width: traffic ? `${Math.min(100, Math.round(((traffic.upload + traffic.download) / (traffic.total || 1)) * 100))}%` : '0%' }} /></div>
<div className="jms-traffic-meta">
{traffic
? `${((traffic.upload + traffic.download) / 1e9).toFixed(1)}/${(traffic.total / 1e9).toFixed(0)}GB · 剩余${((traffic.total - traffic.upload - traffic.download) / 1e9).toFixed(1)}GB`
: '还没有流量数据,点设置填订阅地址'}
</div>
{subSettingsOpen && (
<div className="jms-traffic-settings">
{traffic && traffic.expire && <div className="field-label" style={{ marginBottom: 6 }}>下次重置{new Date(traffic.expire * 1000).toLocaleDateString()}</div>}
<span className="field-label">标准订阅地址</span>
<input className="input" value={subUrl} onChange={e => onSubUrlChange(e.target.value)} placeholder="用于同步真实流量" />
</div>
)}
</div>
</div>
<div className="jms-grid">