Explorar o código

Merge branch 'master' of http://1.14.104.71:10880/txl/ylrz_saas_his_scrm_adminUI

lmx hai 6 días
pai
achega
e7dd9f6867

+ 18 - 0
src/api/tenant/tenant.js

@@ -76,3 +76,21 @@ export function menuEdit(data) {
     data: data
   })
 }
+
+// 查询租户配置
+export function getTenantConfigByKey(configKey, id) {
+  return request({
+    url: '/tenant/tenant/getConfigByKey/' + configKey,
+    method: 'get',
+    params: { id }
+  })
+}
+
+// 修改租户配置
+export function editTenantConfig(data) {
+  return request({
+    url: '/tenant/tenant/config/edit',
+    method: 'post',
+    data: data
+  })
+}

+ 1 - 1
src/components/H5/FormWrapper.vue

@@ -116,7 +116,7 @@ export default {
 }
 </script>
 
-<style>
+<style scoped>
 .el-form-item__label{
   text-align: center !important;
 }

+ 3 - 3
src/views/course/userCourse/index.vue

@@ -686,9 +686,9 @@ export default {
     });
 
 
-    getSelectableRange().then(e => {
-      this.startTimeRange = e.data;
-    })
+    // getSelectableRange().then(e => {
+    //   this.startTimeRange = e.data;
+    // })
     // this.getTreeselect();
     this.getDicts("sys_spec_show").then(response => {
       this.specShowOptions = response.data;

+ 3 - 3
src/views/course/userCourse/public.vue

@@ -601,9 +601,9 @@ export default {
     });
 
 
-    getSelectableRange().then(e => {
-      this.startTimeRange = e.data;
-    })
+    // getSelectableRange().then(e => {
+    //   this.startTimeRange = e.data;
+    // })
     // this.getTreeselect();
     this.getDicts("sys_spec_show").then(response => {
       this.specShowOptions = response.data;

+ 1 - 1
src/views/live/liveAnchor/index.vue

@@ -356,7 +356,7 @@ export default {
   }
 };
 </script>
-<style >
+<style scoped>
 .el-form-item__label {
   width: 120px !important;
 }

+ 58 - 20
src/views/saas/tenant/config/index.vue

@@ -27,6 +27,7 @@
 
 <script>
 import {getConfigByKey, updateConfigByKey} from '@/api/system/config';
+import {editTenantConfig, getTenantConfigByKey} from '@/api/tenant/tenant';
 import yaml from "js-yaml";
 import MonacoEditor from "vue-monaco";
 
@@ -37,11 +38,17 @@ export default {
     MonacoEditor
   },
 
+  props: {
+    tenantId: {
+      type: [String, Number],
+      default: null
+    }
+  },
+
   data() {
     return {
       activeName: "projectConfig",
       projectYaml: "",
-      tenantId: null,
       monacoOptions: {
         automaticLayout: true,
         minimap: { enabled: false },
@@ -65,20 +72,39 @@ export default {
 
   methods: {
     loadConfig(key) {
-      getConfigByKey(key).then(response => {
-        if (!response.data) {
-          this.configId = null;
-          this.projectYaml = '';
-          return;
-        }
-
-        const data = response.data;
-        this.configId = data.configId;
-
-        // JSON.parse 将 JSON 字符串变对象,再用 yaml.dump 转 YAML 字符串
-        const obj = JSON.parse(data.configValue);
-        this.projectYaml = yaml.dump(obj);
-      });
+      if (this.tenantId) {
+        getTenantConfigByKey(key, this.tenantId).then(response => {
+          if (response.data) {
+            const data = response.data;
+            this.configId = data.configId;
+            const obj = JSON.parse(data.configValue);
+            this.projectYaml = yaml.dump(obj);
+          } else {
+            // 租户配置为空,回退到系统配置接口
+            this.configId = null;
+            this.projectYaml = '';
+            getConfigByKey(key).then(res => {
+              if (res.data) {
+                this.configId = res.data.configId;
+                const obj = JSON.parse(res.data.configValue);
+                this.projectYaml = yaml.dump(obj);
+              }
+            });
+          }
+        });
+      } else {
+        getConfigByKey(key).then(response => {
+          if (!response.data) {
+            this.configId = null;
+            this.projectYaml = '';
+            return;
+          }
+          const data = response.data;
+          this.configId = data.configId;
+          const obj = JSON.parse(data.configValue);
+          this.projectYaml = yaml.dump(obj);
+        });
+      }
     },
 
     /** Tab 切换时强制 layout */
@@ -99,11 +125,23 @@ export default {
         }
         const param = { configId: this.configId, configKey: this.activeName, configValue: JSON.stringify(json) }
 
-        updateConfigByKey(param).then(response => {
-          if (response.code === 200) {
-            this.msgSuccess('修改成功')
-          }
-        })
+        if (this.tenantId) {
+          // 从租户管理页面进入,使用租户配置编辑接口
+          param.id = this.tenantId;
+          editTenantConfig(param).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess('修改成功');
+              this.$emit('success');
+            }
+          })
+        } else {
+          // 默认使用系统配置接口
+          updateConfigByKey(param).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess('修改成功')
+            }
+          })
+        }
       } catch (e) {
         this.$message.error("YAML 格式错误:" + e.message);
       }

+ 330 - 147
src/views/saas/tenant/index.vue

@@ -1,160 +1,177 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="租户编码" prop="tenantCode">
-        <el-input
-          v-model="queryParams.tenantCode"
-          placeholder="请输入租户编码"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="租户名称" prop="tenantName">
-        <el-input
-          v-model="queryParams.tenantName"
-          placeholder="请输入租户名称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="状态:" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择状态:1-启用,0-禁用" clearable size="small">
-          <el-option
-            v-for="dict in statusOptions"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
+  <div class="app-container tenant-page">
+    <div class="page-card search-card" v-show="showSearch">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px" class="search-form">
+        <el-form-item label="租户编码" prop="tenantCode">
+          <el-input
+            v-model="queryParams.tenantCode"
+            placeholder="请输入租户编码"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="租户名称" prop="tenantName">
+          <el-input
+            v-model="queryParams.tenantName"
+            placeholder="请输入租户名称"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery"
           />
-        </el-select>
-      </el-form-item>
-
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          icon="el-icon-plus"
-          size="mini"
-          @click="handleAdd"
-          v-hasPermi="['tenant:tenant:add']"
-        >新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="success"
-          plain
-          icon="el-icon-edit"
-          size="mini"
-          :disabled="single"
-          @click="handleUpdate"
-          v-hasPermi="['tenant:tenant:edit']"
-        >修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="danger"
-          plain
-          icon="el-icon-delete"
-          size="mini"
-          :disabled="multiple"
-          @click="handleDelete"
-          v-hasPermi="['tenant:tenant:remove']"
-        >删除</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          plain
-          icon="el-icon-download"
-          size="mini"
-          :loading="exportLoading"
-          @click="handleExport"
-          v-hasPermi="['tenant:tenant:export']"
-        >导出</el-button>
-      </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table border v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="租户唯一ID" align="center" prop="id" />
-      <el-table-column label="租户编码" align="center" prop="tenantCode" />
-      <el-table-column label="租户名称" align="center" prop="tenantName" />
-      <el-table-column label="状态" align="center" prop="status">
-        <template slot-scope="scope">
-          <dict-tag :options="statusOptions" :value="scope.row.status"/>
-        </template>
-      </el-table-column>
-      <el-table-column label="租户有效期" align="center" prop="expireTime" width="180">
-        <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
-        </template>
-      </el-table-column>
-<!--      <el-table-column label="数据库连接地址" align="center" prop="dbUrl" />-->
-<!--      <el-table-column label="数据库帐号" align="center" prop="dbAccount" />-->
-<!--      <el-table-column label="数据库密码" align="center" prop="dbPwd" />-->
-      <el-table-column label="联系电话" align="center" prop="contactPhone" />
-      <el-table-column label="联系人" align="center" prop="contactName" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
+        </el-form-item>
+        <el-form-item label="状态:" prop="status">
+          <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+            <el-option
+              v-for="dict in statusOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="page-card toolbar-card">
+      <el-row :gutter="10" class="mb8 toolbar-row">
+        <el-col :span="1.5">
           <el-button
+            type="primary"
+            plain
+            icon="el-icon-plus"
             size="mini"
-            type="text"
+            @click="handleAdd"
+            v-hasPermi="['tenant:tenant:add']"
+          >新增</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="success"
+            plain
             icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
+            size="mini"
+            :disabled="single"
+            @click="handleUpdate"
             v-hasPermi="['tenant:tenant:edit']"
           >修改</el-button>
+        </el-col>
+        <el-col :span="1.5">
           <el-button
-            size="mini"
-            type="text"
+            type="danger"
+            plain
             icon="el-icon-delete"
-            @click="handleDelete(scope.row)"
+            size="mini"
+            :disabled="multiple"
+            @click="handleDelete"
             v-hasPermi="['tenant:tenant:remove']"
           >删除</el-button>
+        </el-col>
+        <el-col :span="1.5">
           <el-button
+            type="warning"
+            plain
+            icon="el-icon-download"
             size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleMenuChange(scope.row,'sys')"
-            v-hasPermi="['tenant:tenant:edit']"
-          >修改后台菜单权限</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleComMenuChange(scope.row,'com')"
-            v-hasPermi="['tenant:tenant:edit']"
-          >修改销售菜单权限</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
+            :loading="exportLoading"
+            @click="handleExport"
+            v-hasPermi="['tenant:tenant:export']"
+          >导出</el-button>
+        </el-col>
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+    </div>
+
+    <div class="page-card table-card">
+      <el-table border stripe highlight-current-row v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange" class="tenant-table">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="租户唯一ID" align="center" prop="id" />
+        <el-table-column label="租户编码" align="center" prop="tenantCode" />
+        <el-table-column label="租户名称" align="center" prop="tenantName" />
+        <el-table-column label="状态" align="center" prop="status">
+          <template slot-scope="scope">
+            <dict-tag :options="statusOptions" :value="scope.row.status"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="租户有效期" align="center" prop="expireTime" width="180">
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <!--      <el-table-column label="数据库连接地址" align="center" prop="dbUrl" />-->
+        <!--      <el-table-column label="数据库帐号" align="center" prop="dbAccount" />-->
+        <!--      <el-table-column label="数据库密码" align="center" prop="dbPwd" />-->
+        <el-table-column label="联系电话" align="center" prop="contactPhone" />
+        <el-table-column label="联系人" align="center" prop="contactName" />
+        <el-table-column label="创建时间" align="center" prop="createTime" />
+        <el-table-column label="更新时间" align="center" prop="updateTime" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <div class="table-action-group">
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-edit"
+                @click="handleUpdate(scope.row)"
+                v-hasPermi="['tenant:tenant:edit']"
+              >修改</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-delete"
+                @click="handleDelete(scope.row)"
+                v-hasPermi="['tenant:tenant:remove']"
+              >删除</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-edit"
+                @click="handleMenuChange(scope.row,'sys')"
+                v-hasPermi="['tenant:tenant:edit']"
+              >后台菜单权限</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-edit"
+                @click="handleComMenuChange(scope.row,'com')"
+                v-hasPermi="['tenant:tenant:edit']"
+              >销售菜单权限</el-button>
+              <el-button
+                size="mini"
+                type="text"
+                icon="el-icon-setting"
+                @click="handleConfig(scope.row)"
+                v-hasPermi="['tenant:config:edit']"
+              >修改配置</el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
 
     <!-- 添加或修改租户基础信息对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="650px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+    <el-dialog :title="title" :visible.sync="open" width="650px" append-to-body class="tenant-dialog">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px" class="tenant-data-form">
         <el-form-item label="租户编码" prop="tenantCode">
           <el-input v-model="form.tenantCode" placeholder="请输入租户编码" />
         </el-form-item>
         <el-form-item label="租户名称" prop="tenantName">
           <el-input v-model="form.tenantName" placeholder="请输入租户名称" />
         </el-form-item>
-        <el-form-item label="状态:" v-if="isAdd">
+        <!-- <el-form-item label="状态:" prop="status">
           <el-radio-group v-model="form.status">
             <el-radio
               v-for="dict in statusOptions"
@@ -162,7 +179,7 @@
               :label="parseInt(dict.dictValue)"
             >{{dict.dictLabel}}</el-radio>
           </el-radio-group>
-        </el-form-item>
+        </el-form-item> -->
         <el-form-item label="租户有效期" prop="expireTime" label-width="100px">
           <el-date-picker clearable size="small"
                           v-model="form.expireTime"
@@ -171,10 +188,10 @@
                           placeholder="选择租户有效期">
           </el-date-picker>
         </el-form-item>
-<!--        <el-form-item label="数据库连接地址" prop="dbUrl">-->
-<!--          <el-input v-model="form.dbUrl" placeholder="请输入数据库连接地址" />-->
-<!--        </el-form-item>-->
-        <el-form-item label="数据库ip地址" prop="dbIp" label-width="100px">
+        <!--        <el-form-item label="数据库连接地址" prop="dbUrl">-->
+        <!--          <el-input v-model="form.dbUrl" placeholder="请输入数据库连接地址" />-->
+        <!--        </el-form-item>-->
+        <el-form-item label="数据库IP" prop="dbIp" label-width="100px">
           <el-input v-model="form.dbIp" placeholder="请输入数据库ip地址" />
         </el-form-item>
         <el-form-item label="数据库端口" prop="dbPort" label-width="100px">
@@ -203,7 +220,7 @@
     </el-dialog>
 
     <!-- 修改菜单权限 -->
-    <el-dialog :title="title" :visible.sync="openMenu" width="500px" append-to-body>
+    <el-dialog :title="title" :visible.sync="openMenu" width="500px" append-to-body class="tenant-dialog">
       <el-form ref="form" :model="form" :rules="rules" label-width="100px">
         <el-form-item label="菜单权限">
           <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
@@ -226,6 +243,11 @@
         <el-button @click="cancelMenu">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 修改租户配置 -->
+    <el-dialog title="修改配置" :visible.sync="openConfig" width="900px" append-to-body class="tenant-dialog">
+      <config-index v-if="openConfig" :tenant-id="configTenantId" @success="handleConfigSuccess" />
+    </el-dialog>
   </div>
 </template>
 
@@ -242,9 +264,13 @@ import {
 } from "@/api/tenant/tenant";
 import {code} from "quill/ui/icons";
 import {getRole} from "@/api/system/role";
+import ConfigIndex from "./config/index";
 
 export default {
   name: "Tenant",
+  components: {
+    ConfigIndex
+  },
   data() {
     return {
       // 遮罩层
@@ -296,11 +322,32 @@ export default {
           { required: true, message: "租户名称不能为空", trigger: "blur" }
         ],
         status: [
-          { required: true, message: "状态:1-启用,0-禁用不能为空", trigger: "blur" }
+          { required: true, message: "状态不能为空", trigger: "change" }
+        ],
+        expireTime: [
+          { required: true, message: "租户有效期不能为空", trigger: "change" }
+        ],
+        dbIp: [
+          { required: true, message: "数据库ip地址不能为空", trigger: "blur" }
         ],
-        createTime: [
-          { required: true, message: "创建时间不能为空", trigger: "blur" }
+        dbPort: [
+          { required: true, message: "数据库端口不能为空", trigger: "blur" }
         ],
+        dbName: [
+          { required: true, message: "数据库名称不能为空", trigger: "blur" }
+        ],
+        dbAccount: [
+          { required: true, message: "数据库帐号不能为空", trigger: "blur" }
+        ],
+        dbPwd: [
+          { required: true, message: "数据库密码不能为空", trigger: "blur" }
+        ],
+        contactPhone: [
+          { required: true, message: "联系电话不能为空", trigger: "blur" }
+        ],
+        contactName: [
+          { required: true, message: "联系人不能为空", trigger: "blur" }
+        ]
       },
       menuExpand: false,
       menuNodeAll: false,
@@ -312,6 +359,8 @@ export default {
         label: "label"
       },
       isAdd: false,
+      openConfig: false,
+      configTenantId: null,
     };
   },
   created() {
@@ -343,6 +392,9 @@ export default {
         tenantName: null,
         status: 0,
         expireTime: null,
+        dbIp: null,
+        dbPort: null,
+        dbName: null,
         dbUrl: null,
         dbAccount: null,
         dbPwd: null,
@@ -560,11 +612,11 @@ export default {
       })
     },
 
-    handleComMenuChange(row,flag) {
+    handleComMenuChange(row, flag) {
       this.reset();
       this.form.tenantId = row.id;
       this.menuFlag = flag;
-      let data = {id: row.id, flag:flag}
+      let data = { id: row.id, flag: flag }
       roleMenuTreeselect(data).then(res => {
         this.openMenu = true;
         this.menuOptions = res.menus;
@@ -589,6 +641,137 @@ export default {
         });
       })
     },
+
+    /** 修改配置按钮操作 */
+    handleConfig(row) {
+      this.configTenantId = row.id;
+      this.openConfig = true;
+    },
+    /** 配置保存成功回调 */
+    handleConfigSuccess() {
+      this.openConfig = false;
+    },
   }
 };
 </script>
+
+<style scoped>
+.tenant-page {
+  background: #f5f7fb;
+  min-height: calc(100vh - 84px);
+  padding: 16px;
+}
+
+.page-card {
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 8px 20px rgba(25, 57, 99, 0.08);
+  padding: 16px 16px 8px;
+  margin-bottom: 14px;
+}
+
+.search-card {
+  border: 1px solid #eef2f8;
+}
+
+.toolbar-card {
+  padding: 12px 16px 4px;
+}
+
+.toolbar-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+}
+
+.tenant-page .search-form ::v-deep .el-form-item {
+  margin-bottom: 12px;
+}
+
+.table-card {
+  padding: 14px 14px 12px;
+}
+
+.tenant-page .tenant-table ::v-deep .el-table__header th {
+  background: linear-gradient(180deg, #f7faff, #f3f8ff);
+  color: #2f3f5c;
+  font-weight: 600;
+  height: 44px;
+}
+
+.tenant-page .tenant-table ::v-deep .el-table__body tr:hover > td {
+  background: #f7fbff !important;
+}
+
+.tenant-page .tenant-table ::v-deep .el-table__body td {
+  padding: 10px 0;
+}
+
+.tenant-page .tenant-table ::v-deep .el-table__body tr.current-row > td {
+  background: #eef6ff !important;
+}
+
+.table-action-group {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  gap: 6px 8px;
+}
+
+.tenant-page .table-action-group ::v-deep .el-button--text {
+  background: #f5f8ff;
+  border: 1px solid #e7eefb;
+  border-radius: 999px;
+  padding: 4px 10px;
+  line-height: 1.2;
+}
+
+.tenant-page .tenant-dialog ::v-deep .el-dialog {
+  border-radius: 14px;
+  overflow: hidden;
+}
+
+.tenant-page .tenant-dialog ::v-deep .el-dialog__header {
+  background: linear-gradient(90deg, #f5f9ff, #ffffff);
+  border-bottom: 1px solid #edf1f7;
+}
+
+.tenant-page .tenant-dialog ::v-deep .el-dialog__body {
+  padding: 20px 24px 12px;
+}
+
+.tenant-data-form {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+.tenant-page .tenant-data-form ::v-deep .el-form-item {
+  width: calc(50% - 10px);
+}
+
+.tenant-page .tenant-data-form ::v-deep .el-input__inner,
+.tenant-page .tenant-data-form ::v-deep .el-date-editor.el-input,
+.tenant-page .tenant-data-form ::v-deep .el-date-editor.el-input__inner {
+  width: 100%;
+}
+
+.tenant-page .tenant-data-form ::v-deep .el-radio-group {
+  width: 100%;
+  min-height: 34px;
+  display: flex;
+  align-items: center;
+}
+
+.tenant-page .tenant-dialog ::v-deep .dialog-footer .el-button {
+  min-width: 84px;
+  border-radius: 8px;
+}
+
+.tenant-page .tree-border {
+  border-radius: 8px;
+  border-color: #e6edf7;
+  padding: 8px;
+  background: #fbfdff;
+}
+</style>

+ 54 - 38
src/views/system/config/config.vue

@@ -3663,8 +3663,23 @@ export default {
     close() {
       this.product.open = false
     },
+    safeParseConfig(configValue, fallback = {}) {
+      if (configValue === null || configValue === undefined || configValue === '' || configValue === 'null') {
+        return fallback
+      }
+      try {
+        const parsed = JSON.parse(configValue)
+        if (parsed === null || parsed === undefined) {
+          return fallback
+        }
+        return parsed
+      } catch (e) {
+        return fallback
+      }
+    },
     getConfigByKey(key) {
       getConfigByKey(key).then(response => {
+        const configValue = response && response.data ? response.data.configValue : null
         if(!!response.data){
           this.configId = response.data.configId
           this.configKey = response.data.configKey
@@ -3679,39 +3694,39 @@ export default {
           }
         }
         if (key == 'sys.oss.cloudStorage') {
-          this.form1 = JSON.parse(response.data.configValue)
+          this.form1 = this.safeParseConfig(configValue, { ...this.form1 })
         }
         if (key == 'his.inquiryConfig') {
-          this.form2 = JSON.parse(response.data.configValue)
+          this.form2 = this.safeParseConfig(configValue, { ...this.form2 })
         }
         if (key == 'his.agreementConfig') {
-          this.form3 = JSON.parse(response.data.configValue)
+          this.form3 = this.safeParseConfig(configValue, { ...this.form3 })
         }
         if (key == 'his.certs') {
-          this.form4 = JSON.parse(response.data.configValue)
+          this.form4 = this.safeParseConfig(configValue, { ...this.form4 })
           if (this.form4.certs != null) {
             this.photoArr = this.form4.certs.split(',')
           }
         }
         if (key == 'his.brokerage') {
-          this.form5 = JSON.parse(response.data.configValue)
+          this.form5 = this.safeParseConfig(configValue, { ...this.form5 })
         }
         if (key == 'his.coupon') {
           allList().then(response => {
             this.couponList = response.data
           })
-          this.form6 = JSON.parse(response.data.configValue)
+          this.form6 = this.safeParseConfig(configValue, { ...this.form6 })
 
         }
         if (key == 'his.store') {
-          this.form7 = JSON.parse(response.data.configValue)
+          this.form7 = this.safeParseConfig(configValue, { ...this.form7 })
           getAllFollowTempName().then(response => {
             this.tempOptions = response.rows
           })
         }
         if (key == 'his.store') {
           // this.form7 =JSON.parse(response.data.configValue);
-          const parsed = JSON.parse(response.data.configValue)
+          const parsed = this.safeParseConfig(configValue, { ...this.form7 })
           if (parsed.isUpdateOrder != null) {
             parsed.isUpdateOrder = Number(parsed.isUpdateOrder)
           } else {
@@ -3720,71 +3735,72 @@ export default {
           this.form7 = parsed
         }
         if (key == 'his.package') {
-          this.form8 = JSON.parse(response.data.configValue)
+          this.form8 = this.safeParseConfig(configValue, { ...this.form8 })
         }
         if (key == 'his.pay') {
-          this.form9 = JSON.parse(response.data.configValue)
+          this.form9 = this.safeParseConfig(configValue, { ...this.form9 })
         }
         if (key == 'store.pay') {
-          this.form23 = JSON.parse(response.data.configValue)
+          this.form23 = this.safeParseConfig(configValue, { ...this.form23 })
         }
         if (key == 'his.appShow') {
-          this.form10 = JSON.parse(response.data.configValue)
+          this.form10 = this.safeParseConfig(configValue, { ...this.form10 })
         }
         if (key == 'his.integral') {
           return
         }
         if (key == 'his.sign') {
-          this.form12 = JSON.parse(response.data.configValue)
+          this.form12 = this.safeParseConfig(configValue, { ...this.form12 })
         }
         if (key == 'his.config') {
           this.getCompanyOptions()
-          this.form13 = JSON.parse(response.data.configValue)
+          this.form13 = this.safeParseConfig(configValue, { ...this.form13 })
 
         }
         if (key == 'store.config') {
-          this.form17 = JSON.parse(response.data.configValue)
+          this.form17 = this.safeParseConfig(configValue, { ...this.form17 })
         }
         if (key == 'his.sms') {
-          this.form14 = JSON.parse(response.data.configValue)
+          this.form14 = this.safeParseConfig(configValue, { ...this.form14 })
         }
         if (key == 'qw:config') {
-          this.form15 = JSON.parse(response.data.configValue)
+          this.form15 = this.safeParseConfig(configValue, { ...this.form15 })
         }
         if (key == 'his.brand') {
-          this.form16 = JSON.parse(response.data.configValue)
+          this.form16 = this.safeParseConfig(configValue, { ...this.form16 })
           console.log(this.form16)
         }
         if (key == 'course.config') {
-          this.form18 = JSON.parse(response.data.configValue)
+          this.form18 = this.safeParseConfig(configValue, { ...this.form18 })
         }
         if (key == 'redPacket.config') {
-          this.form19 = JSON.parse(response.data.configValue)
+          this.form19 = this.safeParseConfig(configValue, { ...this.form19 })
         }
         if (key == 'qwRating.config') {
-          this.form20 = JSON.parse(response.data.configValue)
+          this.form20 = this.safeParseConfig(configValue, { ...this.form20 })
         }
         if (key == 'courseMa.config') {
           this.courseMaConfigLoading = true
           if (response.data && response.data.configValue) {
-            this.courseMaConfigList = JSON.parse(response.data.configValue).map(item => ({
+            const parsedCourseMaConfigList = this.safeParseConfig(configValue, [])
+            this.courseMaConfigList = Array.isArray(parsedCourseMaConfigList) ? parsedCourseMaConfigList.map(item => ({
               ...item,
               editing: false
-            }))
+            })) : []
           } else {
             this.courseMaConfigList = []
           }
           this.courseMaConfigLoading = false
         }
         if (key == 'his.login') {
-          this.form22 = JSON.parse(response.data.configValue)
+          this.form22 = this.safeParseConfig(configValue, { ...this.form22 })
         }
         if (key === 'his.healthIndexConfig') {
-          this.form26 = JSON.parse(response.data.configValue)
+          this.form26 = this.safeParseConfig(configValue, { ...this.form26 })
         } else if (key == 'store.concept') {
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form21 = JSON.parse(response.data.configValue)
+          this.form21 = this.safeParseConfig(configValue, { ...this.form21 })
           if (this.form21.images != null) {
             this.images = this.form21.images.split(',')
           }
@@ -3792,48 +3808,48 @@ export default {
           this.configId = response.data.configId
           this.configKey = response.data.configKey
           console.log(response.data.configValue)
-          this.form24 = JSON.parse(response.data.configValue)
+          this.form24 = this.safeParseConfig(configValue, { ...this.form24 })
           console.log(this.form24.sign)
         } else if (key == 'app.config') {
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form25 = JSON.parse(response.data.configValue)
+          this.form25 = this.safeParseConfig(configValue, { ...this.form25 })
           if (this.form25.images != null) {
             this.appImages = this.form25.images.split(',')
           }
         }else if (key == 'medicalMall.func.switch') {
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form27 = {...this.form27, ...JSON.parse(response.data.configValue)}
+          this.form27 = {...this.form27, ...this.safeParseConfig(configValue, {})}
           console.log(this.form27)
         }
         if (key == 'his.zzzs') {
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form28 = {...this.form28, ...JSON.parse(response.data.configValue)}
+          this.form28 = {...this.form28, ...this.safeParseConfig(configValue, {})}
         }
         if (key == 'randomRedpacket:config') {
           if(!!response.data){
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form29 = {...this.form29, ...JSON.parse(response.data.configValue)}
+          this.form29 = {...this.form29, ...this.safeParseConfig(configValue, {})}
           }
         }
         if (key == 'wx.config') {
           if(!!response.data){
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form30 = {...this.form30, ...JSON.parse(response.data.configValue)}
+          this.form30 = {...this.form30, ...this.safeParseConfig(configValue, {})}
           }
         }
 
         if(key=="luckyBag.config"){
           console.log("----------"+response.data.configValue)
-          this.form36 =JSON.parse(response.data.configValue);
+          this.form36 = this.safeParseConfig(configValue, { ...this.form36 });
         }
         if (key == 'his.AppRedPacket') {
           if (response.data && response.data.configValue) {
-            this.form35 = JSON.parse(response.data.configValue);
+            this.form35 = this.safeParseConfig(configValue, { ...this.form35 });
           } else {
             // 如果没有配置,使用默认值
             this.form35 = {
@@ -3853,27 +3869,27 @@ export default {
           this.configKey = response.data?.configKey || 'his.AppRedPacket';
         }
         if(key=="im.config"){
-          this.form34 =JSON.parse(response.data.configValue);
+          this.form34 = this.safeParseConfig(configValue, { ...this.form34 });
         }
         if(key == 'vc.config'){
            if(!!response.data){
           this.configId = response.data.configId
           this.configKey = response.data.configKey
-          this.form31 = {...this.form31, ...JSON.parse(response.data.configValue)}
+          this.form31 = {...this.form31, ...this.safeParseConfig(configValue, {})}
           }
         }
         if(key == 'living.config'){
           if(!!response.data){
             this.configId = response.data.configId
             this.configKey = response.data.configKey
-            this.form32 = {...this.form32, ...JSON.parse(response.data.configValue)}
+            this.form32 = {...this.form32, ...this.safeParseConfig(configValue, {})}
             console.log(this.form32 );
           }
         }if(key == 'his.adminUi.config'){
           if(!!response.data){
             this.configId = response.data.configId
             this.configKey = response.data.configKey
-            this.form33 = {...this.form33, ...JSON.parse(response.data.configValue)}
+            this.form33 = {...this.form33, ...this.safeParseConfig(configValue, {})}
           }
         }
         if(key == "cId.config" && !!this.form40.enableGateWayLimit){

+ 31 - 21
src/views/system/config/integralConfig.vue

@@ -162,28 +162,30 @@ import { getConfigByKey, updateConfigByKey } from '@/api/system/config'
 export default {
   name: "IntegralConfig",
   data() {
+    const defaultForm11 = {
+      integralRegister: null,
+      integralFinishConsultation: null,
+      integralAddPatient: null,
+      integralAddUserAddress: null,
+      integralInvite: null,
+      integralInvited: null,
+      integralNewTask: null,
+      integralRatio: null,
+      integralShare: null,
+      integralFollow: null,
+      integralCourse: null,
+      integralProduct: null,
+      integralFirstVideo: null,
+      integralFinishVideo: null,
+      integralByOneDay: null,
+      integralFirstOrderPoint: null,
+      integralTypeByOneDay: null,
+      integralSubscriptCourse: null,
+    }
     return {
       integralLogTypeOptions: [],
-      form11: {
-        integralRegister: null,
-        integralFinishConsultation: null,
-        integralAddPatient: null,
-        integralAddUserAddress: null,
-        integralInvite: null,
-        integralInvited: null,
-        integralNewTask: null,
-        integralRatio: null,
-        integralShare: null,
-        integralFollow: null,
-        integralCourse: null,
-        integralProduct: null,
-        integralFirstVideo: null,
-        integralFinishVideo: null,
-        integralByOneDay: null,
-        integralFirstOrderPoint: null,
-        integralTypeByOneDay: null,
-        integralSubscriptCourse: null,
-      },
+      defaultForm11,
+      form11: { ...defaultForm11 },
       saveLoading: false,
     }
   },
@@ -198,7 +200,15 @@ export default {
       getConfigByKey(key).then(response => {
         this.configId=response.data.configId;
         this.configKey=response.data.configKey;
-        this.form11 =JSON.parse(response.data.configValue);
+        let parsed = null
+        try {
+          parsed = JSON.parse(response.data.configValue)
+        } catch (e) {
+          parsed = null
+        }
+        this.form11 = parsed && typeof parsed === 'object'
+          ? { ...this.defaultForm11, ...parsed }
+          : { ...this.defaultForm11 }
       });
     },
     submitForm11(){