]> acesimba.cloud Git - StationBuilder-vue.git/commitdiff
领域配置:新增编辑按钮+默认颜色配置,修复透明弹窗;领域方案筛选栏增加查询按钮
authorroot <root@localhost.localdomain>
Mon, 28 Sep 2026 03:44:36 +0000 (11:44 +0800)
committerroot <root@localhost.localdomain>
Mon, 28 Sep 2026 03:44:36 +0000 (11:44 +0800)
src-vue/Home.vue
src-vue/pages/DomainSettings.vue
src-vue/pages/domain/CompanyDomainLibrary.vue
src-vue/styles/ui-system.css

index 9ad4273171aed7e83eeded1860bfbbcced4da800..ca42329497137d78e8b46df9fe2d0f16dff3664e 100644 (file)
@@ -236,11 +236,12 @@ onMounted(() => {
 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 () => {
@@ -619,7 +620,7 @@ function onMenuOpenChange(keys) { resourceNavOpen.value = keys.includes("resourc
           <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>
index 0b1aba370a6e54edcbbe35bd876e4b398a2337dc..53327bd8e8fe937f32a66940fcec1fb60cb207e8 100644 (file)
@@ -11,7 +11,7 @@ import Panel from "../components/Panel.vue";
 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";
@@ -21,6 +21,20 @@ const ElButton = PrimaryButton;
 // 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);
@@ -58,6 +72,7 @@ const props = defineProps({
   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();
@@ -73,6 +88,31 @@ async function remove(row) {
   }
 }
 
+// ---- 领域编辑:名称 + 默认颜色配置 ----
+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,
@@ -590,12 +630,18 @@ const OpsFlowsViewer = {
           </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>
@@ -603,5 +649,33 @@ const OpsFlowsViewer = {
         <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>
index bc45e6c5bc691b8b5e6535eeb89851d760eb1122..8c8ba83f0baac710dcd7236c41b259d5b0320e2c 100644 (file)
@@ -5,6 +5,8 @@ import { Search, OfficeBuilding, Close, DocumentCopy, Menu } from "@element-plus
 import UiButton from "../../ui/UiButton.vue";
 import UiInput from "../../ui/UiInput.vue";
 import UiSelect from "../../ui/UiSelect.vue";
+import UiActions from "../../ui/UiActions.vue";
+import PrimaryButton from "../../components/PrimaryButton.vue";
 import { fetchDb, domainTagColor } from "../../lib/api.js";
 import { friendlyDomainName, domainScene, SOLUTION_PHOTOS, statusTone } from "./domainCommon.js";
 
@@ -79,6 +81,9 @@ const statusText = (row) => (row.status === "available" ? "可用" : "不可用"
         <option value="">全部领域</option>
         <option v-for="d in domainOptions" :key="d.id" :value="d.id">{{ friendlyDomainName(d.name) }}</option>
       </UiSelect>
+      <UiActions class="filter-actions">
+        <PrimaryButton @click="load">查询</PrimaryButton>
+      </UiActions>
     </div>
     <div v-if="loading" class="empty-state">正在加载领域方案…</div>
     <div v-else class="domain-showcase-grid">
index 6ca7fd42720f812e29b7490b90ae3340095a746d..2880de8ecc1a8ecdeb1f8a89ad6d7154156caa98 100644 (file)
@@ -82,13 +82,22 @@ html,body{font-family:var(--ant-font-family);font-size:var(--ant-font-size);colo
 .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}