优化复刻视频脚本和下拉框样式

This commit is contained in:
Azmat@qq.com
2026-08-31 10:59:54 +08:00
parent 284748eeff
commit f59e9d0418
20 changed files with 775 additions and 212 deletions
+50 -25
View File
@@ -7,6 +7,7 @@ import type { NavigateFn } from "./route-config";
import { money, pts, stageMeta, yuan } from "./stage-config";
import { pageWindow } from "../components/pager";
import { useBodyScrollLock, useOverlayTransition } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
const ROLE_LABEL: Record<string, string> = { owner: "超管", admin: "团管", member: "成员", viewer: "访客" };
const STATUS_LABEL: Record<string, string> = { active: "活跃", invited: "待激活", disabled: "已停用" };
@@ -466,19 +467,31 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className={`tab-panel ${tab === "bills" ? "active" : ""}`}>
<div className="filter-bar">
<select value={billType} onChange={(e) => { setBillType(e.target.value); setBillPage(1); }} aria-label="按类型筛选">
<option value="all"></option>
<option value="charge"></option>
<option value="recharge"></option>
<option value="reserve"></option>
<option value="release"></option>
<option value="adjustment"></option>
<option value="refund">退</option>
</select>
<select value={billMember} onChange={(e) => { setBillMember(e.target.value); setBillPage(1); }} aria-label="按成员筛选">
<option value="all"></option>
{billMemberOptions.map((m) => <option key={m.id} value={m.id}>{m.label}</option>)}
</select>
<CustomSelect
size="sm"
aria-label="按类型筛选"
value={billType}
onChange={(next) => { setBillType(next); setBillPage(1); }}
options={[
{ value: "all", label: "全部类型" },
{ value: "charge", label: "扣费" },
{ value: "recharge", label: "充值" },
{ value: "reserve", label: "预扣" },
{ value: "release", label: "释放" },
{ value: "adjustment", label: "调整" },
{ value: "refund", label: "退款" },
]}
/>
<CustomSelect
size="sm"
aria-label="按成员筛选"
value={billMember}
onChange={(next) => { setBillMember(next); setBillPage(1); }}
options={[
{ value: "all", label: "全部成员" },
...billMemberOptions.map((m) => ({ value: m.id, label: m.label })),
]}
/>
{billFiltered && (
<button className="filter-reset" type="button" onClick={() => { setBillType("all"); setBillMember("all"); setBillPage(1); }}></button>
)}
@@ -539,12 +552,18 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className={`tab-panel ${tab === "by-project" ? "active" : ""}`}>
<div className="filter-bar">
<select value={projStatus} onChange={(e) => setProjStatus(e.target.value as typeof projStatus)} aria-label="按状态筛选">
<option value="all"></option>
<option value="wip"></option>
<option value="ok"></option>
<option value="fail"> · </option>
</select>
<CustomSelect
size="sm"
aria-label="按状态筛选"
value={projStatus}
onChange={(next) => setProjStatus(next as typeof projStatus)}
options={[
{ value: "all", label: "全部状态" },
{ value: "wip", label: "进行中" },
{ value: "ok", label: "已完成" },
{ value: "fail", label: "失败 · 待重跑" },
]}
/>
{projFiltered && (
<button className="filter-reset" type="button" onClick={() => setProjStatus("all")}></button>
)}
@@ -578,12 +597,18 @@ export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
<div className={`tab-panel ${tab === "by-member" ? "active" : ""}`}>
<div className="filter-bar">
<select value={memRole} onChange={(e) => setMemRole(e.target.value as typeof memRole)} aria-label="按角色筛选">
<option value="all"></option>
<option value="owner"></option>
<option value="admin"></option>
<option value="member"></option>
</select>
<CustomSelect
size="sm"
aria-label="按角色筛选"
value={memRole}
onChange={(next) => setMemRole(next as typeof memRole)}
options={[
{ value: "all", label: "全部角色" },
{ value: "owner", label: "超管" },
{ value: "admin", label: "团管" },
{ value: "member", label: "成员" },
]}
/>
{memFiltered && (
<button className="filter-reset" type="button" onClick={() => setMemRole("all")}></button>
)}
@@ -4,6 +4,7 @@ import { adminApi } from "../../api";
import { Pager } from "../../components/pager";
import { IconKitSvg } from "../../components/IconKitSvg";
import { SystemLoading } from "../../components/loading";
import { CustomSelect } from "../../components/custom-select";
import type { AdminLedger, AdminQuotaPolicy, AdminTeam } from "../../types";
import { pts } from "../stage-config";
@@ -214,10 +215,13 @@ export function AdminLedgersPage({ notify }: { notify: Notify }) {
<p className="admin-modal-desc"> / ();</p>
<div className="field">
<label className="field-label"> <span className="req">*</span></label>
<select className="select" value={form.team} onChange={(e) => setForm((f) => ({ ...f, team: e.target.value }))}>
<option value=""></option>
{teams.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
</select>
<CustomSelect
fill
value={form.team}
placeholder="选择团队…"
onChange={(next) => setForm((f) => ({ ...f, team: next }))}
options={teams.map((t) => ({ value: t.id, label: t.name }))}
/>
</div>
<div className="field">
<label className="field-label"> <span className="field-hint">=,=</span></label>
@@ -372,10 +376,14 @@ export function AdminQuotaPage({ notify }: { notify: Notify }) {
<p className="admin-modal-desc"> = ()</p>
<div className="field">
<label className="field-label"> <span className="req">*</span></label>
<select className="select" value={editing.team} disabled={Boolean(editing.id)} onChange={(e) => setEditing((p) => ({ ...p, team: e.target.value }))}>
<option value=""></option>
{teams.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
</select>
<CustomSelect
fill
value={editing.team}
disabled={Boolean(editing.id)}
placeholder="选择团队…"
onChange={(next) => setEditing((p) => ({ ...p, team: next }))}
options={teams.map((t) => ({ value: t.id, label: t.name }))}
/>
</div>
<div className="field-row">
<div className="field">
@@ -3,6 +3,7 @@ import { Server, X } from "lucide-react";
import { adminApi } from "../../api";
import { Pager } from "../../components/pager";
import { SystemLoading } from "../../components/loading";
import { CustomSelect } from "../../components/custom-select";
import type { AdminModel, AdminProvider } from "../../types";
import { pts } from "../stage-config";
@@ -229,10 +230,14 @@ export function AdminModelsPage({ notify }: { notify: Notify }) {
<div className="modal-b">
<div className="field">
<label className="field-label"> <span className="req">*</span></label>
<select className="select" value={modelModal.provider} disabled={Boolean(modelModal.id)} onChange={(e) => setModelModal((m) => m && ({ ...m, provider: e.target.value }))}>
<option value=""></option>
{providers.map((p) => <option key={p.id} value={p.id}>{p.display_name}</option>)}
</select>
<CustomSelect
fill
value={modelModal.provider}
disabled={Boolean(modelModal.id)}
placeholder="选择供应商…"
onChange={(next) => setModelModal((m) => m && ({ ...m, provider: next }))}
options={providers.map((p) => ({ value: p.id, label: p.display_name }))}
/>
</div>
<div className="field-row">
<div className="field">
@@ -241,9 +246,13 @@ export function AdminModelsPage({ notify }: { notify: Notify }) {
</div>
<div className="field">
<label className="field-label"></label>
<select className="select" value={modelModal.capability} disabled={Boolean(modelModal.id)} onChange={(e) => setModelModal((m) => m && ({ ...m, capability: e.target.value }))}>
{CAPABILITIES.map((c) => <option key={c} value={c}>{c}</option>)}
</select>
<CustomSelect
fill
value={modelModal.capability}
disabled={Boolean(modelModal.id)}
onChange={(next) => setModelModal((m) => m && ({ ...m, capability: next }))}
options={CAPABILITIES.map((c) => ({ value: c, label: c }))}
/>
</div>
</div>
<div className="field">
+61 -33
View File
@@ -9,6 +9,7 @@ import { isPublicGenerationError, presentGenerationError } from "../generation-e
import type { Notice, Page } from "./route-config";
import { stageOrder, statusPill } from "./stage-config";
import { ConfirmModal, MediaLightbox, TeamModal, useBodyScrollLock } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
import { DEFAULT_BILLING_RATES, estimateCost, FC_MODELS, modelLabel } from "../components/free-create/constants";
import { ModelLibrary } from "../components/model-library";
import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
@@ -1198,25 +1199,33 @@ export function PipelinePage(props: {
return (
<div className="as-spec-fields" aria-label="成片规格">
<label className="as-spec-field">
<select className="select" value={outputAspect} onChange={(event) => changeOutputSpec({ aspect_ratio: event.target.value })}>
{OUTPUT_RATIOS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</select>
<CustomSelect fill size="sm" value={outputAspect} onChange={(next) => changeOutputSpec({ aspect_ratio: next })} options={OUTPUT_RATIOS} />
</label>
<label className="as-spec-field">
<select className="select" value={outputResolution} onChange={(event) => changeOutputSpec({ resolution: event.target.value })}>
{OUTPUT_RESOLUTIONS.map((option) => (
<option key={option.value} value={option.value} disabled={Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value))}>
{option.label}
</option>
))}
</select>
<CustomSelect
fill
size="sm"
value={outputResolution}
onChange={(next) => changeOutputSpec({ resolution: next })}
options={OUTPUT_RESOLUTIONS.map((option) => ({
...option,
disabled: Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value)),
}))}
/>
</label>
<label className="as-spec-field">
<select className="select" value={outputModelId} onChange={(event) => changeOutputSpec({ video_model_config_id: event.target.value })} disabled={!videoConfigs.length}>
{videoConfigs.length ? videoConfigs.map((config) => (
<option key={config.id} value={config.id}>{FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name)}</option>
)) : <option value=""></option>}
</select>
<CustomSelect
fill
size="sm"
value={outputModelId}
disabled={!videoConfigs.length}
placeholder="暂无模型"
onChange={(next) => changeOutputSpec({ video_model_config_id: next })}
options={videoConfigs.map((config) => ({
value: config.id,
label: FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name),
}))}
/>
</label>
</div>
);
@@ -3222,27 +3231,42 @@ export function PipelinePage(props: {
沿,{setupProduct?.title || "当前商品"}
</div>
)}
<label className="setup-field">
<div className="setup-field">
<span className="sf-k"></span>
<select className="setup-select" value={setupStructure} onChange={(e) => { setupTouched.current = true; setSetupStructure(e.target.value as VideoStructure); }}>
{structureOptions.map((k) => <option key={k} value={k}>{VIDEO_STRUCTURES[k]}</option>)}
</select>
</label>
<CustomSelect
fill
size="sm"
className="setup-select"
value={setupStructure}
onChange={(next) => { setupTouched.current = true; setSetupStructure(next as VideoStructure); }}
options={structureOptions.map((k) => ({ value: k, label: VIDEO_STRUCTURES[k] }))}
/>
</div>
<div className="setup-rec">{STRUCTURE_HINT[setupStructure]}</div>
<label className="setup-field">
<div className="setup-field">
<span className="sf-k"></span>
<select className="setup-select" value={setupPersona} onChange={(e) => { setupTouched.current = true; setSetupPersona(e.target.value); }}>
{SETUP_PERSONA_KEYS.map((k) => <option key={k} value={k}>{WIZ_PERSONA_LABEL[k]}</option>)}
</select>
</label>
<CustomSelect
fill
size="sm"
className="setup-select"
value={setupPersona}
onChange={(next) => { setupTouched.current = true; setSetupPersona(next); }}
options={SETUP_PERSONA_KEYS.map((k) => ({ value: k, label: WIZ_PERSONA_LABEL[k] }))}
/>
</div>
<div className="setup-rec">{recommended.reason} · {WIZ_PERSONA_LABEL[recommended.persona] || recommended.persona}</div>
{/* 时长:15/30/45/60;每镜固定 15 秒 */}
<label className="setup-field">
<div className="setup-field">
<span className="sf-k"></span>
<select className="setup-select" value={setupDuration} onChange={(e) => { setupTouched.current = true; setSetupDuration(clampDuration(Number(e.target.value))); }}>
{DURATION_OPTIONS.map((s) => <option key={s} value={s}>{s} </option>)}
</select>
</label>
<CustomSelect
fill
size="sm"
className="setup-select"
value={String(setupDuration)}
onChange={(next) => { setupTouched.current = true; setSetupDuration(clampDuration(Number(next))); }}
options={DURATION_OPTIONS.map((s) => ({ value: String(s), label: `${s}` }))}
/>
</div>
<div className={`setup-rec${durationHint ? " warn" : ""}`}>{durationHint || `口播 × ${VIDEO_STRUCTURES[setupStructure]}建议 ${durationSuggest} 秒左右`}</div>
<div className="setup-foot">
{/* ← 返回:收起设定卡,回到「脚本辅助生成 / 上传脚本」入口页 */}
@@ -4101,9 +4125,13 @@ export function PipelinePage(props: {
{voStale && <div style={{ fontSize: "12px", color: "#B45309", marginBottom: 6 }}>,,</div>}
<div className="props-row" style={{ marginBottom: 6 }}>
<span className="k"></span>
<select value={voVoicePick || voInfo?.voice_type || VO_VOICES[0].key} onChange={(e) => setVoVoicePick(e.target.value)} style={{ flex: 1, fontSize: "12px", padding: "4px 6px", border: "1px solid var(--border-faint)", borderRadius: "6px", background: "var(--surface)", color: "var(--accent-black)" }}>
{VO_VOICES.map((v) => <option key={v.key} value={v.key}>{v.label}</option>)}
</select>
<CustomSelect
fill
size="sm"
value={voVoicePick || voInfo?.voice_type || VO_VOICES[0].key}
onChange={setVoVoicePick}
options={VO_VOICES.map((v) => ({ value: v.key, label: v.label }))}
/>
</div>
<div style={{ display: "flex", gap: 6 }}>
<button className="btn btn-sm" type="button" disabled={loading || !edState.clips.some((c) => c.subtitle.trim())} onClick={() => onGenerateVoiceover({ items: edState.clips.map((c, idx) => ({ index: idx, text: c.subtitle.trim() })).filter((item) => item.text), voice_type: voVoicePick || voInfo?.voice_type || VO_VOICES[0].key })}>{voInfo ? "重新生成配音" : `生成配音 · ${pts(10)} 积分/500字`}</button>
+30 -20
View File
@@ -6,6 +6,7 @@ import { ConfirmModal, MediaLightbox, SuccessModal } from "../components/overlay
import { useFileDrop } from "../components/use-file-drop";
import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
import { ProductCreateDrawer } from "../components/product-create-drawer";
import { CustomSelect } from "../components/custom-select";
import {
BUSINESS_TYPES,
BUSINESS_TYPE_KEYS,
@@ -527,15 +528,16 @@ export function ProductCreateUploadPage({ onCreate, onBack }: { onCreate: (paylo
</div>
<div className="field">
<label className="field-label"><span className="req">*</span></label>
<select className="select" value={category} onChange={(event) => setCategory(event.target.value)} required>
<option value=""> </option>
<optgroup label="常用品类">
{PC_CAT_PRIMARY.map((option) => <option key={option}>{option}</option>)}
</optgroup>
<optgroup label="更多品类">
{PC_CAT_MORE.map((option) => <option key={option}>{option}</option>)}
</optgroup>
</select>
<CustomSelect
fill
value={category}
onChange={setCategory}
placeholder="— 选择品类 —"
options={[
...PC_CAT_PRIMARY.map((option) => ({ value: option, label: option, group: "常用品类" })),
...PC_CAT_MORE.map((option) => ({ value: option, label: option, group: "更多品类" })),
]}
/>
</div>
<div className="field field-last">
<label className="field-label">()</label>
@@ -1069,23 +1071,31 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
<div className="k"></div>
<div className="v">
<span className="v-static">{BUSINESS_TYPES[realType]}</span>
<select className="v-edit v-select" value={bizType} onChange={(event) => {
const next = event.target.value as BusinessType;
setBizType(next);
const opts = pdCatChoices(next, "");
if (!opts.includes(cat)) setCat(opts[0]);
}}>
{BUSINESS_TYPE_KEYS.map((key) => <option key={key} value={key}>{BUSINESS_TYPES[key]}</option>)}
</select>
<CustomSelect
fill
className="v-edit"
value={bizType}
onChange={(next) => {
const typed = next as BusinessType;
setBizType(typed);
const opts = pdCatChoices(typed, "");
if (!opts.includes(cat)) setCat(opts[0]);
}}
options={BUSINESS_TYPE_KEYS.map((key) => ({ value: key, label: BUSINESS_TYPES[key] }))}
/>
</div>
</div>
<div className="row" data-field="cat">
<div className="k"></div>
<div className="v">
<span className="v-static">{realCat}</span>
<select className="v-edit v-select" value={cat} onChange={(event) => setCat(event.target.value)}>
{pdCatChoices(bizType, cat).map((option) => <option key={option}>{option}</option>)}
</select>
<CustomSelect
fill
className="v-edit"
value={cat}
onChange={setCat}
options={pdCatChoices(bizType, cat).map((option) => ({ value: option, label: option }))}
/>
</div>
</div>
<div className="row" data-field="bullets">
+14 -6
View File
@@ -5,6 +5,7 @@ import type { Asset, Product, Project, ScriptTemplate } from "../types";
import { api } from "../api";
import type { Page } from "./route-config";
import { ConfirmModal, MediaLightbox } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
import { ProductCreateDrawer, type ProductCreatePayload } from "../components/product-create-drawer";
import { isLocalLife } from "../product-business";
import { Pager } from "../components/pager";
@@ -325,12 +326,19 @@ export function ProjectWizardPage({ products, projects = [], preselectProductId,
{templates.length > 0 && (
<label className="nw-field">
<span><small></small></span>
<select value={templateId} onChange={(event) => setTemplateId(event.target.value)}>
<option value=""> · AI </option>
{templates.map((item) => (
<option key={item.id} value={item.id}>{item.name} · {item.total_duration}s{item.usage_count ? ` · 用过 ${item.usage_count}` : ""}</option>
))}
</select>
<CustomSelect
fill
value={templateId}
onChange={setTemplateId}
placeholder="不套用 · 让 AI 按这个商品从头设计"
options={[
{ value: "", label: "不套用 · 让 AI 按这个商品从头设计" },
...templates.map((item) => ({
value: item.id,
label: `${item.name} · ${item.total_duration}s${item.usage_count ? ` · 用过 ${item.usage_count}` : ""}`,
})),
]}
/>
{template && (
<div className="nw-tpl">
<strong></strong>
+30 -18
View File
@@ -20,6 +20,7 @@ import {
} from "lucide-react";
import { api, ApiError, type QuickCreateJob } from "../api";
import { ConfirmModal, OverlayPortal, useBodyScrollLock } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
import { useFileDrop } from "../components/use-file-drop";
import { forgetQuickCreateJob, readQuickCreateJobId, rememberQuickCreateJob, stripQuickCreateSuffix } from "../quick-create-lock";
import type { ModelConfig } from "../types";
@@ -678,33 +679,44 @@ export function QuickCreatePage({
<div className="quick-parameter-grid" aria-label="视频核心参数">
<label className="quick-parameter-field">
<span></span>
<select value={aspectRatio} onChange={(event) => setAspectRatio(event.target.value)} disabled={isGenerating}>
{QUICK_RATIOS.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</select>
<CustomSelect fill value={aspectRatio} onChange={setAspectRatio} disabled={isGenerating} options={QUICK_RATIOS} />
</label>
<label className="quick-parameter-field">
<span></span>
<select value={resolution} onChange={(event) => setResolution(event.target.value)} disabled={isGenerating}>
{QUICK_RESOLUTIONS.map((option) => (
<option key={option.value} value={option.value} disabled={Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value))}>
{option.label}
</option>
))}
</select>
<CustomSelect
fill
value={resolution}
onChange={setResolution}
disabled={isGenerating}
options={QUICK_RESOLUTIONS.map((option) => ({
...option,
disabled: Boolean(supportedResolutions.length && !supportedResolutions.includes(option.value)),
}))}
/>
</label>
<label className="quick-parameter-field">
<span></span>
<select value={totalDuration} onChange={(event) => setTotalDuration(Number(event.target.value))} disabled={isGenerating}>
{QUICK_DURATIONS.map((duration) => <option key={duration} value={duration}>{duration / 15} {duration}s</option>)}
</select>
<CustomSelect
fill
value={String(totalDuration)}
onChange={(next) => setTotalDuration(Number(next))}
disabled={isGenerating}
options={QUICK_DURATIONS.map((duration) => ({ value: String(duration), label: `${duration / 15} 场(${duration}s` }))}
/>
</label>
<label className="quick-parameter-field">
<span></span>
<select value={videoModelId} onChange={(event) => setVideoModelId(event.target.value)} disabled={isGenerating || !videoConfigs.length}>
{videoConfigs.length ? videoConfigs.map((config) => (
<option key={config.id} value={config.id}>{FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name)}</option>
)) : <option value=""></option>}
</select>
<CustomSelect
fill
value={videoModelId}
onChange={setVideoModelId}
disabled={isGenerating || !videoConfigs.length}
placeholder="暂无可用视频模型"
options={videoConfigs.map((config) => ({
value: config.id,
label: FC_MODELS.find((item) => item.name === config.name)?.label || config.display_name || modelLabel(config.name),
}))}
/>
</label>
</div>
+27 -26
View File
@@ -10,6 +10,7 @@ import {
} from "lucide-react";
import type { LoginSession, Team, User, UserPreference } from "../types";
import { TeamModal } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
import { useFileDrop } from "../components/use-file-drop";
type SectionKey = "profile" | "security" | "notify" | "pref" | "display";
@@ -620,23 +621,23 @@ export function SettingsPage({
<div className="form-row">
<div className="lbl"> BGM </div>
<div className="val">
<select className="select" value={draft.bgm} onChange={(event) => patchDraft("bgm", event.target.value)}>
<option value="kapian"> Top10 </option>
<option value="emotion"> · /</option>
<option value="urban"> · </option>
<option value="none"> BGM</option>
</select>
<CustomSelect fill value={draft.bgm} onChange={(next) => patchDraft("bgm", next)} options={[
{ value: "kapian", label: "抖音 Top10 卡点曲库" },
{ value: "emotion", label: "情绪向 · 治愈/悬念" },
{ value: "urban", label: "都市电子 · 通勤场景" },
{ value: "none", label: "无 BGM" },
]} />
</div>
</div>
<div className="form-row">
<div className="lbl"></div>
<div className="val">
<select className="select" value={draft.transition} onChange={(event) => patchDraft("transition", event.target.value)}>
<option value="none"></option>
<option value="fade"> · 0.3s</option>
<option value="slide"> · 0.3s</option>
<option value="zoom"> · 0.3s</option>
</select>
<CustomSelect fill value={draft.transition} onChange={(next) => patchDraft("transition", next)} options={[
{ value: "none", label: "无转场" },
{ value: "fade", label: "淡入淡出 · 0.3s" },
{ value: "slide", label: "滑动 · 0.3s" },
{ value: "zoom", label: "缩放 · 0.3s" },
]} />
</div>
</div>
<div className="form-row">
@@ -658,30 +659,30 @@ export function SettingsPage({
<div className="form-row">
<div className="lbl"></div>
<div className="val">
<select className="select" value={draft.appearance} onChange={(event) => patchDraft("appearance", event.target.value)}>
<option value="system"></option>
<option value="light"></option>
<option value="dark" disabled>(V2)</option>
</select>
<CustomSelect fill value={draft.appearance} onChange={(next) => patchDraft("appearance", next)} options={[
{ value: "system", label: "跟随系统" },
{ value: "light", label: "浅色" },
{ value: "dark", label: "深色(V2)", disabled: true },
]} />
</div>
</div>
<div className="form-row">
<div className="lbl"></div>
<div className="val">
<select className="select" value={draft.language} onChange={(event) => patchDraft("language", event.target.value)}>
<option value="zh"></option>
<option value="en" disabled>English(V2)</option>
</select>
<CustomSelect fill value={draft.language} onChange={(next) => patchDraft("language", next)} options={[
{ value: "zh", label: "简体中文" },
{ value: "en", label: "English(V2)", disabled: true },
]} />
</div>
</div>
<div className="form-row">
<div className="lbl"></div>
<div className="val">
<select className="select" value={draft.density} onChange={(event) => patchDraft("density", event.target.value)}>
<option value="compact"></option>
<option value="standard"></option>
<option value="loose"></option>
</select>
<CustomSelect fill value={draft.density} onChange={(next) => patchDraft("density", next)} options={[
{ value: "compact", label: "紧凑" },
{ value: "standard", label: "标准" },
{ value: "loose", label: "宽松" },
]} />
</div>
</div>
</section>
+10 -4
View File
@@ -5,6 +5,7 @@ import type { BillingSummary, BillingTrend, Invitation, Notification, Team, Team
import type { Page } from "./route-config";
import { money } from "./stage-config";
import { ConfirmModal, TeamModal } from "../components/overlays";
import { CustomSelect } from "../components/custom-select";
import { Pager } from "../components/pager";
const MEMBERS_PER_PAGE = 10;
@@ -608,10 +609,15 @@ export function TeamPage({ team, user, billing, navigate, onCreateMember: onCrea
<>
<div className="field">
<label className="field-label"></label>
<select className="input" value={invRole} onChange={(e) => setInvRole(e.target.value)}>
<option value="member"> · ,</option>
<option value="admin"> · //</option>
</select>
<CustomSelect
fill
value={invRole}
onChange={setInvRole}
options={[
{ value: "member", label: "成员 · 可用生成,不可管理团队" },
{ value: "admin", label: "团队管理员 · 可管成员/额度/邀请" },
]}
/>
</div>
<div className="field" style={{ marginBottom: 0 }}>
<label className="field-label">()<span className="lbl-note">(-1 )</span></label>