const reloadDomains = async () => {
try {
const d = await fetchDb("/domains"); const arr = d || [];
- domainOptions.value = arr.map((x) => ({ id: x.id, name: x.name }));
+ domainOptions.value = arr.map((x) => ({ id: x.id, name: x.name, color: x.color }));
domains.value = arr.map((x) => x.name);
} catch (e) { notify("加载领域失败: " + e.ElMessage); }
};
const handleDomainDeleted = async (id) => { await reloadDomains(); if (domainFilter.value === id) domainFilter.value = null; };
+const handleDomainUpdated = async () => { await reloadDomains(); };
const loadSite = async (id) => { const data = await fetchDb(`/sites/${id}`); site.value = data; siteId.value = id; };
const createSite = async () => {
<ResourcePlaceholder v-if="page === 'ops-template'" title="运维模板" />
<ResourcePlaceholder v-if="page === 'knowledge-template'" title="知识模板" />
<ToolSettings v-if="page === 'toolSettings'" :model="model" :on-model-change="(v) => (model = v)" :notify="notify" />
- <DomainSettings v-if="page === 'domainSettings'" :domains="domainOptions" :notify="notify" :on-deleted="handleDomainDeleted" />
+ <DomainSettings v-if="page === 'domainSettings'" :domains="domainOptions" :notify="notify" :on-deleted="handleDomainDeleted" :on-updated="handleDomainUpdated" />
<SiteConfigurationContext v-if="page === 'siteEdit' && site" :site="site" :on-back="backToHierarchy" />
<div v-if="page === 'basic' || (page === 'siteEdit' && siteEditSection === 'basic')" class="station-overview-layout"><Basic :site="site" :domains="domains" :on-change="saveSiteBasic" :on-add-domain="addDomain" :on-upload="uploadFile" :saving="saving" :notify="notify" :on-view-complete-prompt="viewCompletePrompt" :go="go" :site-id="siteId" :site-hierarchy="siteHierarchy" /></div>
<div v-if="(page === 'assets' || (page === 'siteEdit' && siteEditSection === 'assets')) && site" class="station-unified-assets"><ManualAssetConfiguration :key="site.id" :site="site" :notify="notify" :on-site-change="(s) => (site = s)" :register-save="(save) => (assetSaveRef = save)" /></div>
import Field from "../components/Field.vue";
import StatusTag from "../components/StatusTag.vue";
import PrimaryButton from "../components/PrimaryButton.vue";
-import { fetchDb, loadRenderer } from "../lib/api.js";
+import { fetchDb, loadRenderer, domainTagColor } from "../lib/api.js";
import { MODELS } from "../lib/meta.js";
import { useConfirm } from "../pages/domain/useConfirm.js";
import { DeviceImageField, DeviceTemplateCard, CP_RANGE_PRESETS, fileToBase64 } from "../components/DeviceTemplateCard.js";
// React `Status` (ElTag with tone class).
const Status = StatusTag;
+// 领域默认颜色配置:skill(web-ui-builder / antd 设计体系)预设色板,
+// 与领域方案首页标签同一套配色;未显式设置颜色时按名称回退到 domainTagColor。
+const DOMAIN_COLORS = [
+ { value: "blue", label: "蓝" },
+ { value: "cyan", label: "青" },
+ { value: "orange", label: "橙" },
+ { value: "green", label: "绿" },
+ { value: "purple", label: "紫" },
+ { value: "red", label: "红" },
+];
+function domainColor(row) {
+ return row.color || domainTagColor(row.name);
+}
+
const CURRENT_USER_EMAIL = "wang.yu@advantech.com";
function useStreamComplete() {
const completing = ref(false);
domains: { type: Array, default: () => [] },
notify: { type: Function, default: () => {} },
onDeleted: { type: Function, default: () => Promise.resolve() },
+ onUpdated: { type: Function, default: () => Promise.resolve() },
});
const { confirm, el: confirmEl } = useConfirm();
}
}
+// ---- 领域编辑:名称 + 默认颜色配置 ----
+const editing = ref(null);
+const editName = ref("");
+const editColor = ref("");
+function openEdit(row) {
+ editing.value = row;
+ editName.value = row.name;
+ editColor.value = domainColor(row);
+}
+async function saveEdit() {
+ if (!editName.value.trim()) { props.notify("请填写领域名称"); return; }
+ try {
+ await fetchDb(`/domains/${editing.value.id}`, {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ name: editName.value.trim(), color: editColor.value }),
+ });
+ props.notify("已保存领域");
+ editing.value = null;
+ if (props.onUpdated) await props.onUpdated();
+ } catch (e) {
+ props.notify("保存失败:" + (e && e.ElMessage ? e.ElMessage : e));
+ }
+}
+
// ============================================================================
// Co-located port of every component defined in app/page.tsx lines 1543–1928.
// (DomainLibrary, CreateTemplate, DeviceImageField, DeviceTemplateCard,
</thead>
<tbody>
<tr v-for="row in domains" :key="row.id">
- <td><b>{{ row.name }}</b></td>
+ <td>
+ <span class="domain-color-dot" :class="'tag-' + domainColor(row)"></span>
+ <b>{{ row.name }}</b>
+ </td>
<td>领域方案、站点项目</td>
<td><Status :value="row.name === '其他' ? '系统预置' : '可用'" /></td>
<td>
- <span v-if="row.name === '其他'" class="muted">不可删除</span>
- <UiButton v-else class="text-button danger" @click="() => remove(row)">删除</UiButton>
+ <span v-if="row.name === '其他'" class="muted">系统预置</span>
+ <template v-else>
+ <UiButton class="text-button" @click="() => openEdit(row)">编辑</UiButton>
+ <UiButton class="text-button danger" @click="() => remove(row)">删除</UiButton>
+ </template>
</td>
</tr>
</tbody>
<div v-if="domains.length === 0" class="empty-state">暂无领域,请点击右上角“新增领域”创建</div>
</div>
</Panel>
+
+ <UiOverlay v-if="editing" className="dlg-backdrop" :onMouseDown="() => (editing = null)">
+ <div class="dlg-box domain-edit-dialog" @mousedown.stop>
+ <h2>编辑领域</h2>
+ <label class="field">
+ <span class="field-label">领域名称</span>
+ <UiInput :value="editName" maxLength="6" placeholder="请输入领域名称(最多 6 个字)" @change="(e) => (editName = e.target.value)" />
+ </label>
+ <label class="field">
+ <span class="field-label">默认颜色</span>
+ <div class="domain-color-picker">
+ <button
+ v-for="c in DOMAIN_COLORS"
+ :key="c.value"
+ type="button"
+ class="domain-color-swatch"
+ :class="['tag-' + c.value, { active: editColor === c.value }]"
+ :title="c.label"
+ @click="() => (editColor = c.value)"
+ ></button>
+ </div>
+ </label>
+ <UiActions className="dlg-actions">
+ <PrimaryButton secondary @click="() => (editing = null)">取消</PrimaryButton>
+ <PrimaryButton :disabled="!editName.trim()" @click="saveEdit">保存</PrimaryButton>
+ </UiActions>
+ </div>
+ </UiOverlay>
</div>
</template>
.station-inline-actions,.station-modal-actions,.station-drawer-actions,.drawer-actions,.result-actions,.settings-actions{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--ant-padding-xs)}
.station-modal-actions{justify-content:flex-end}
/* Element Plus Dialog 承载定位、聚焦与外框;旧类名只标识业务内容。 */
-.station-ui-modal.modal,.station-ui-modal.dlg-box{display:block;position:relative;inset:auto;margin-inline:auto;padding:0;max-width:calc(100vw - 32px);max-height:none;height:auto;background:none;border:0;border-radius:0;box-shadow:none;overflow:visible;z-index:auto;color:var(--ant-color-text-base);font-size:var(--ant-font-size)}
+.station-ui-modal.modal,.station-ui-modal.dlg-box{display:block;position:relative;inset:auto;margin-inline:auto;padding:0;max-width:calc(100vw - 32px);max-height:none;height:auto;background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);border-radius:var(--ant-border-radius-lg);box-shadow:0 12px 36px color-mix(in srgb,var(--ant-color-text-base) 20%,transparent);overflow:visible;z-index:auto;color:var(--ant-color-text-base);font-size:var(--ant-font-size)}
.station-ui-modal .el-dialog{padding:var(--ant-padding-lg);border-radius:var(--ant-border-radius-lg)}
.station-ui-modal .el-dialog__title{font-size:var(--ant-font-size-heading-4);font-weight:600}
.station-ui-modal .el-dialog__body{font-size:var(--ant-font-size);color:var(--ant-color-text-base)}
.station-ui-modal .modal-body{padding:0;max-height:70vh;overflow:auto}
.station-ui-modal .el-dialog__footer{margin-top:var(--ant-padding-lg)}
.station-ui-modal .station-modal-actions .el-button{margin:0}
+/* 领域配置:默认颜色配置(swatch / dot),复用 antd 预设色板(web-ui-builder 设计体系) */
+.domain-color-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:8px;vertical-align:middle;border:1px solid color-mix(in srgb,var(--ant-color-text-base) 10%,transparent)}
+.domain-color-dot.tag-blue{background:#1677ff}.domain-color-dot.tag-cyan{background:#13c2c2}.domain-color-dot.tag-orange{background:#fa8c16}.domain-color-dot.tag-green{background:#52c41a}.domain-color-dot.tag-purple{background:#722ed1}.domain-color-dot.tag-red{background:#f5222d}.domain-color-dot.tag-default{background:#8c8c8c}
+.domain-edit-dialog .field{display:block;margin-bottom:16px}
+.domain-edit-dialog .field-label{display:block;font-size:var(--ant-font-size-sm);color:var(--ant-color-text-secondary);margin-bottom:6px}
+.domain-color-picker{display:flex;gap:10px;flex-wrap:wrap}
+.domain-color-swatch{width:28px;height:28px;border-radius:var(--ant-border-radius);border:1px solid var(--ant-color-border);cursor:pointer;padding:0;transition:box-shadow .15s}
+.domain-color-swatch.tag-blue{background:#1677ff}.domain-color-swatch.tag-cyan{background:#13c2c2}.domain-color-swatch.tag-orange{background:#fa8c16}.domain-color-swatch.tag-green{background:#52c41a}.domain-color-swatch.tag-purple{background:#722ed1}.domain-color-swatch.tag-red{background:#f5222d}.domain-color-swatch.tag-default{background:#8c8c8c}
+.domain-color-swatch.active{box-shadow:0 0 0 2px var(--ant-color-bg-container),0 0 0 4px var(--ant-color-primary);border-color:transparent}
.station-ui-select .el-select__wrapper{font-size:var(--ant-font-size);min-height:var(--ant-control-height)}
.filterbar.company-filterbar{height:auto;min-height:48px;flex-wrap:nowrap}
.filterbar.company-filterbar .station-ui-select{flex-shrink:0;width:160px;min-width:160px}