heyujie
2021-07-21 856c3d2047705fd4e08d029c39f2f39816c95177
video max len
5 文件已重命名
2个文件已修改
9个文件已添加
7326 ■■■■ 已修改文件
src/pages/settings/components/AuthorityManagement.vue 265 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/BasicSetting.vue 1147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/CloudNode.vue 226 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/ClusterManagement.vue 885 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/LogManagement.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/RadioSet.vue 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/SystemMaintenance.vue 514 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/index/App.vue 1918 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/components/CloudNode.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/components/switchBar.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/index/App.vue 1831 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/index/main.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/views/NetSettings.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/views/clusterManagement.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/views/generalSettings.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/systemSettings/views/keyboardLanguage.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/settings/components/AuthorityManagement.vue
New file
@@ -0,0 +1,265 @@
<template>
  <div class="s-authority-management">
    <div class="authority-table s-table" v-if="display">
      <el-table
        highlight-current-row
        :data="userList"
        style="width: 100%"
        :header-cell-style="{background:'#f8f8f8',color:'#222222'}"
      >
        <el-table-column align="center" type="index" label="序号" width="100"></el-table-column>
        <el-table-column :align="'center'" prop="username" label="用户名"></el-table-column>
        <el-table-column :align="'center'" prop="role" label="角色">
          <template slot-scope="scope">{{scope.row.sysRoles | roles}}</template>
        </el-table-column>
        <el-table-column label="操作" :align="'center'">
          <template slot-scope="scope">
            <el-tooltip content="编辑" placement="top" popper-class="atooltip">
              <i
                class="el-icon-edit"
                style="font-size: 18px;"
                @click="handleEdit(scope.$index, scope.row)"
              ></i>
            </el-tooltip>
            <el-tooltip content="删除" placement="top" popper-class="atooltip" v-show="false">
              <!-- :disabled="scope.row.sysRoles | roles | isSuper" -->
              <i
                class="el-icon-delete"
                style="font-size: 18px; color:red;"
                @click="handleDelete(scope.$index, scope.row)"
              ></i>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="authority-details" v-if="!display">
      <el-form label-width="80px" :rules="rules" ref="editForm" :model="editForm">
        <el-form-item label="用户名" style="width:580px" prop="username">
          <!-- <el-input v-model="editForm.username" placeholder="请输入" size="small"></el-input> -->
          <span class="m10" v-if="editForm.username == 'admin' || editForm.username == 'basic'">{{editForm.username}}</span>
          <el-input v-model="editForm.username" size="small" v-else></el-input>
        </el-form-item>
        <el-form-item label="新密码" style="width:580px" v-show="loginUser != editForm.username">
          <el-input show-password v-model="editForm.newPwd" placeholder="请输入密码" size="small"></el-input>
        </el-form-item>
        <el-form-item
          label="确认密码"
          style="width:580px"
          prop="checkPass"
          v-show="loginUser != editForm.username"
        >
          <el-input show-password v-model="editForm.checkPass" placeholder="请输入再次输入密码" size="small"></el-input>
        </el-form-item>
        <el-form-item label="权限配置" style="width:580px;">
          <!-- <el-transfer
            id="e-transfer"
            :titles="['全部角色', '当前角色']"
            v-model="editForm.roleIds"
            :props="{key: 'id', label: 'name'}"
            :data="roledata"
          ></el-transfer>-->
          <el-tree
            ref="treeMenus"
            :data="sysMenus"
            :props="props"
            node-key="id"
            :default-checked-keys="userMenus"
            show-checkbox
            check-on-click-node
            default-expand-all
            style="margin-top: 10px;"
          ></el-tree>
        </el-form-item>
        <el-form-item style="width:580px;">
          <el-button type="primary" style="float: right" @click="save" size="small">保存</el-button>
          <el-button type="info" style="margin-right: 10px;float: right" @click="goback" size="small">返回</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>
<script>
import { getUsers, getSysMenus, updataUser, getUserMenus } from "@/api/user"
export default {
  name: "AuthorityManage",
  filters: {
    roles(roles) {
      return roles.map(r => {
        return r.name
      }).join(' ')
    },
    isSuper(roles) {
      return roles.indexOf("超级管理员") >= 0
    }
  },
  data() {
    const generateData = _ => {
      const data = [];
      for (let i = 1; i <= 15; i++) {
        data.push({
          key: i,
          label: `备选项 ${i}`,
          disabled: i % 4 === 0
        });
      }
      return data;
    };
    const validateCheckPass = (rule, value, callback) => {
      if (value !== this.editForm.newPwd) {
        callback(new Error('两次输入密码不一致!'));
      } else {
        callback()
      }
    };
    var checkUserName = (rule, value, callback) => {
      if (value && value !== ''){
        let regEn = /^[A-Za-z_@.]{2,10}$/
        console.log("用户名校验!",value)
        if (!regEn.test(value)) {
          callback(new Error('请输入2位到10位字母的用户名,不能以数字开头,且不能包含汉字'))
        } else {
          callback()
        }
      } else {
        callback()
      }
    }
    return {
      display: true,
      loginUser: JSON.parse(sessionStorage.getItem('userInfo')).username,
      acknewpwd: "",
      sysMenus: [],
      userMenus: [],
      rolevalue: [],
      userList: [],
      editForm: {},
      props: {
        label: 'name'
      },
      rules: {
        username: [
          { validator: checkUserName, trigger: 'blur' }
        ],
        checkPass: [
          { validator: validateCheckPass, trigger: 'change' }
        ]
      }
    };
  },
  mounted() {
    this.fetchUserList();
    this.fetchSysMenus();
    this.initEditForm();
  },
  methods: {
    initEditForm() {
      this.editForm = {
        id: "",
        username: "",
        newPwd: "",
        checkPass: "",
        menuIds: []
      }
    },
    handleEdit(index, row) {
      this.initEditForm()
      this.display = !this.display;
      this.editForm.id = row.id;
      this.editForm.username = row.username;
      this.userMenus = []
      getUserMenus({ userId: row.id }).then(rsp => {
        if (rsp && rsp.success) {
          this.userMenus = rsp.data.menus.map(menu => {
            return menu.id
          })
        }
      })
    },
    handleDelete(index, row) {
      this.$notify({
        type: "warning",
        message: "无法删除该用户"
      })
    },
    goback() {
      this.display = !this.display;
    },
    save() {
      this.$refs.editForm.validate((valid) => {
        if (valid) {
          this.editForm.menuIds = this.$refs.treeMenus.getCheckedKeys()
          updataUser(this.editForm).then(rsp => {
            if (rsp && rsp.success) {
              this.$notify({
                type: "success",
                message: "修改成功"
              })
            }
          })
        }
      });
    },
    fetchUserList() {
      getUsers().then(rsp => {
        if (rsp && rsp.success) {
          this.userList = rsp.data;
        }
      })
    },
    fetchSysMenus() {
      getSysMenus().then(rsp => {
        if (rsp && rsp.success) {
          this.sysMenus = rsp.data;
        }
      })
    }
  }
};
</script>
<style lang="scss">
.s-authority-management {
  height: 100%;
  width: 100%;
  .authority-table,
  .authority-details {
    height: 100%;
    width: 100%;
    margin-top: 40px;
  }
  #e-transfer {
    .el-button--primary {
      color: #fff;
      background-color: #bfbfbf;
      border-color: #bfbfbf;
    }
    .el-button--primary:focus,
    .el-button--primary:hover {
      background: #4c4c4c;
      border-color: #4c4c4c;
      color: #fff;
    }
    .el-transfer-panel
      .el-transfer-panel__header
      .el-checkbox
      .el-checkbox__label {
      font-size: 14px;
    }
  }
  .el-form-item__content {
    text-align: left;
    input {
      max-width: 498px;
    }
  }
}
</style>
src/pages/settings/components/BasicSetting.vue
New file
@@ -0,0 +1,1147 @@
<template>
  <div class="s-basic-setting">
    <el-tabs
      id="e-basic-setting"
      v-model="activeName"
      v-loading="loading"
      :element-loading-text="loadingText"
      type="border-card"
    >
      <!-- 本机信息 -->
      <el-tab-pane
        label="本机信息"
        name="sysInfo"
        v-if="isShow('settings:sysInfo')"
      >
        <el-menu
          :default-openeds="openeds"
          background-color="#fff"
          text-color="#303133"
          active-text-color="#409EFF"
          style="height: 100%"
          class="menu-css"
          @open="menuOpen"
          @close="menuClose"
        >
          <!-- 本机信息 -->
          <el-submenu index="0">
            <template slot="title">
              <b class="tree-font">本机信息</b>
            </template>
            <el-menu-item-group class="item-group">
              <el-form
                :model="sysinfo"
                :rules="rules"
                ref="sysinfo"
                label-width="100px"
              >
                <el-row>
                  <el-col :span="12">
                    <el-form-item label="名称" prop="server_name">
                      <el-input
                        v-model="sysinfo.server_name"
                        placeholder="服务器名称"
                        size="small"
                      ></el-input>
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="端口" prop="server_port">
                      <el-input
                        v-model="sysinfo.server_port"
                        placeholder="WEB服务端口"
                        size="small"
                      ></el-input>
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row>
                  <el-col :span="12">
                    <el-form-item label="IP" prop="ip">
                      <ip-input
                        :ip="sysinfo.ip"
                        @on-blur="sysinfo.ip = arguments[0]"
                      ></ip-input>
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="子网掩码" prop="subMask">
                      <ip-input
                        :ip="sysinfo.subMask"
                        @on-blur="sysinfo.subMask = arguments[0]"
                      ></ip-input>
                    </el-form-item>
                  </el-col>
                </el-row>
                <el-row>
                  <el-col :span="12">
                    <el-form-item label="网关" prop="gateway">
                      <ip-input
                        :ip="sysinfo.gateway"
                        @on-blur="sysinfo.gateway = arguments[0]"
                      ></ip-input>
                    </el-form-item>
                  </el-col>
                  <el-col :span="12">
                    <el-form-item label="DNS" prop="dns">
                      <ip-input
                        :ip="sysinfo.dns"
                        @on-blur="sysinfo.dns = arguments[0]"
                      ></ip-input>
                    </el-form-item>
                  </el-col>
                </el-row>
                <div class="mt15 mb10 save-btn">
                  <el-button type="primary" @click="submitSysinfo" size="small"
                    >保存</el-button
                  >
                </div>
              </el-form>
            </el-menu-item-group>
          </el-submenu>
          <el-submenu index="1">
            <template slot="title">
              <b class="tree-font">详细信息</b>
            </template>
            <el-menu-item-group class="item-group desc-info">
              <el-row :gutter="gutter">
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">设备ID</div>
                  <div class="xiangqing-info">{{ sysinfo.server_id }}</div>
                </el-col>
                <!-- <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">设备编号</div>
                  <div class="xiangqing-info">{{sysinfo.deviceNum}}</div>
                </el-col>-->
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">设备型号</div>
                  <div class="xiangqing-info">{{ sysinfo.deviceModel }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">设备类型</div>
                  <div class="xiangqing-info">{{ sysinfo.deviceDesc }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">通道个数</div>
                  <div class="xiangqing-info">{{ sysinfo.channelCount }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">主控版本</div>
                  <div class="xiangqing-info">{{ sysinfo.masterVersion }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">web版本</div>
                  <div class="xiangqing-info">{{ sysinfo.webVersion }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">硬盘信息</div>
                  <div class="xiangqing-info">{{ sysinfo.disks }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">CPU</div>
                  <div class="xiangqing-info">{{ sysinfo.cpuInfo }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">内存</div>
                  <div class="xiangqing-info">{{ sysinfo.mem }}</div>
                </el-col>
                <el-col :span="12" class="flex-box">
                  <div class="xiangqin-label">运行时间</div>
                  <div class="xiangqing-info">{{ sysinfo.uptime }}</div>
                </el-col>
              </el-row>
            </el-menu-item-group>
          </el-submenu>
          <!-- 事件录像时长 -->
          <el-submenu index="2">
            <template slot="title">
              <b class="tree-font">事件录像时长</b>
            </template>
            <el-menu-item-group class="item-group">
              <el-form
                label-width="150px"
                class="alarmSetting"
                style="padding-left: 5px; padding-right: 5px"
              >
                <el-form-item label="视频截取最短时长" style="width: 724px">
                  <el-slider
                    id="cut_min_duration"
                    @input="min_len = min_video_len"
                    v-model="fakeObj.min"
                    :min="5 / 1.2"
                    :max="100"
                    :step="5 / 1.2"
                    show-stops
                    :show-tooltip="true"
                    :format-tooltip="formatTooltip"
                  ></el-slider>
                  <el-input-number
                    v-model="min_len"
                    @change="fakeObj.min = +(min_len / 1.2)"
                    controls-position="right"
                    :min="5"
                    :max="120"
                    size="small"
                  ></el-input-number
                  >&nbsp;s
                </el-form-item>
                <el-form-item label="视频截取最长时长" style="width: 724px">
                  <el-slider
                    id="cut_max_duration"
                    v-model="fakeObj.max"
                    :min="5 / 1.2"
                    @input="max_len = max_video_len"
                    :max="100"
                    :step="5 / 1.2"
                    show-stops
                    :format-tooltip="formatTooltip"
                    :show-tooltip="true"
                  ></el-slider>
                  <el-input-number
                    v-model="max_len"
                    @change="fakeObj.max = +(max_len / 1.2)"
                    controls-position="right"
                    :min="5"
                    :max="120"
                    size="small"
                  ></el-input-number
                  >&nbsp;s
                </el-form-item>
                <div class="mt15 mb10 save-btn">
                  <el-button type="primary" @click="submitAlarm" size="small"
                    >保存</el-button
                  >
                </div>
              </el-form>
            </el-menu-item-group>
          </el-submenu>
          <!-- 对外服务IP 改名为外部网络(新tab)-->
        </el-menu>
      </el-tab-pane>
      <!-- 时间配置 -->
      <el-tab-pane
        label="时间配置"
        name="timeSet"
        v-if="isShow('settings:timeSet')"
      >
        <el-form label-width="100px">
          <el-form-item label="设备时间">
            {{ equipmentTime }}
          </el-form-item>
          <div style="text-align: left; padding: 10px 0px">
            <div class="time-type">NTP校时</div>
            <div style="padding: 10px 0px">
              <el-radio v-model="syncType" label="1">NTP校时</el-radio>
            </div>
          </div>
          <el-form-item label="服务器地址">
            <ip-input
              :ip="ntpServer"
              @on-blur="ntpServer = arguments[0]"
              :disabled="syncType === '2'"
            ></ip-input>
          </el-form-item>
          <el-form-item label="校时时间间隔" style="width: 41.3%">
            <el-input-number
              v-model.number="timeInterval"
              :min="1"
              :max="60"
              placeholder="请输入"
              size="small"
              :controls="false"
              :disabled="syncType === '2'"
            ></el-input-number
            >&nbsp;&nbsp;&nbsp;分钟
            <el-button
              type="text"
              style="position: absolute; left: 330px"
              :disabled="syncType === '2'"
              @click="testNTP"
              :loading="ntpTestLoading"
              >测试</el-button
            >
          </el-form-item>
          <div style="text-align: left; padding: 10px 0px">
            <div class="time-type">手动校时</div>
            <div style="padding: 10px 0px">
              <el-radio v-model="syncType" label="2">手动校时</el-radio>
            </div>
          </div>
          <el-form-item label="设置时间">
            <el-date-picker
              v-model="settime"
              type="datetime"
              placeholder="选择日期时间"
              size="small"
              value-format="yyyy-MM-dd HH:mm:ss"
              :disabled="syncType === '1'"
            ></el-date-picker>
            <el-checkbox
              v-model="settimeRadio"
              style="margin-left: 12px"
              @change="syncBrowser"
              :disabled="syncType === '1'"
              >同步本计算机时间</el-checkbox
            >
          </el-form-item>
          <el-col :span="12" style="padding-right: 40px">
            <el-form-item>
              <el-button type="primary" @click="submitClock" size="small"
                >保存</el-button
              >
            </el-form-item>
          </el-col>
        </el-form>
      </el-tab-pane>
      <!-- 集群管理 -->
      <el-tab-pane
        label="集群管理"
        name="cluster"
        v-if="isShow('settings:cluster')"
      >
        <cluster-management></cluster-management>
      </el-tab-pane>
      <!-- <el-tab-pane label="外部访问" name="fourth">
        <el-menu
          :default-openeds="openeds"
          background-color="#fff"
          text-color="#303133"
          active-text-color="#409EFF"
          style="height: 100%;"
          class="menu-css"
          @open="menuOpen"
          @close="menuClose"
        >
          <el-submenu index="0">
            <template slot="title">
              <b class="tree-font">外部访问设置</b>
            </template>
            <el-menu-item-group class="item-group">
              <el-form :model="sysinfo" :rules="rules" ref="sysinfo" label-width="100px">
                <div class="flex-box">
                  <label>设置外部IP</label>
                  <div style="width:300px;">
                    <ip-input :ip="ipServer.ip" @on-blur="ipServer.ip = arguments[0]"></ip-input>
                  </div>
                  <el-checkbox label="选用本机IP" size="small" style="margin-left: 20px"></el-checkbox>
                </div>
                <div class="flex-box">
                  <label>域名</label>
                  <el-input size="small" v-model="ipServer.localhost"></el-input>
                </div>
                <div class="flex-box">
                  <label>本地文件端口</label>
                  <el-input size="small" v-model="ipServer.localFilePort"></el-input>
                </div>
                <div class="mt15 mb10 save-btn">
                  <el-button type="primary" @click="submitSysinfo" size="small">保存</el-button>
                </div>
              </el-form>
            </el-menu-item-group>
          </el-submenu>
        </el-menu>
      </el-tab-pane>-->
      <el-tab-pane
        label="权限管理"
        name="permission"
        v-if="isShow('settings:permission')"
      >
        <authority-management
          v-if="activeName === 'permission'"
        ></authority-management>
      </el-tab-pane>
      <el-tab-pane
        label="广播设置"
        name="broadcast"
        v-if="isShow('settings:broadcast')"
      >
        <radio-set v-if="activeName === 'broadcast'"></radio-set>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import {
  getDevInfo,
  getAlarmConfig,
  saveDevInfo,
  saveAlarmConfig,
  getClockInfo,
  saveClockInfo,
  testNTPserver,
  getResourceConfig,
  saveResourceConfig,
} from "@/api/system";
import { isPort, isIPv4 } from "@/scripts/validate";
import ipInput from "@/components/subComponents/IPInput";
import TimeZones from "@/Pool/TimeZones";
import ClusterManagement from "./ClusterManagement";
import AuthorityManagement from "./AuthorityManagement";
import RadioSet from "./RadioSet";
import config from "../../../../package.json";
export default {
  name: "BasicSettings",
  components: {
    ipInput,
    ClusterManagement,
    AuthorityManagement,
    RadioSet,
  },
  computed: {
    min_video_len() {
      // return +(this.fakeObj.min * 1.2).toFixed(0);
      return Math.round(this.fakeObj.min * 1.2);
    },
    max_video_len() {
      return Math.round(this.fakeObj.max * 1.2);
      // return +(this.fakeObj.max * 1.2).toFixed(0);
    },
    isAdmin() {
      if (
        sessionStorage.getItem("userInfo") &&
        sessionStorage.getItem("userInfo") !== ""
      ) {
        let loginName = JSON.parse(sessionStorage.getItem("userInfo")).username;
        return loginName === "superadmin" || loginName === "basic";
      }
      return false;
    },
  },
  directives: {
    focus: {
      inserted: function (el) {
        el.querySelector("input").focus();
      },
    },
  },
  data() {
    return {
      loading: true,
      loadingText: "",
      gutter: 10,
      activeName: "sysInfo",
      timezone: "",
      syncType: "1",
      ntpServer: "",
      equipmentTime: "",
      NYPport: "",
      settime: "",
      timeInterval: 10,
      settimeRadio: false,
      clockTimer: null,
      browserTimer: null,
      timestamp: 0,
      sysinfo: {},
      alarmConf: {},
      min_len: 0,
      max_len: 0,
      fakeObj: {
        min: 0,
        max: 0,
      },
      originNetConfig: {
        ip: "",
        gw: "",
        mask: "",
        dns: "",
      },
      rules: {
        ip: [
          {
            required: true,
            message: "请输入IP地址",
            trigger: "change",
          },
          { validator: isIPv4, trigger: "change" },
        ],
        ServerIp: [
          {
            required: true,
            message: "请输入IP地址",
            trigger: "change",
          },
          { validator: isIPv4, trigger: "change" },
        ],
        ServerPort: [
          {
            required: true,
            message: "请输入端口",
            trigger: "change",
          },
          { validator: isPort, trigger: "change" },
        ],
        GbServerPort: [
          {
            required: true,
            message: "请输入端口",
            trigger: "change",
          },
          { validator: isPort, trigger: "change" },
        ],
        gateway: [
          {
            required: true,
            message: "请输入网关",
            trigger: "change",
          },
          { validator: isIPv4, trigger: "change" },
        ],
        dns: [
          {
            required: true,
            message: "请输入dns地址",
            trigger: "change",
          },
          { validator: isIPv4, trigger: "change" },
        ],
        server_name: [
          { required: true, message: "请输入名称", trigger: "change" },
        ],
        subMask: [
          {
            required: true,
            message: "请输入子网掩码",
            trigger: "change",
          },
          { validator: isIPv4, trigger: "change" },
        ],
      },
      openeds: ["0"],
      ipServer: {
        diyOrLocalIP: "1",
        ip: "",
        localhost: "",
        localFilePort: "",
      },
      locationCity: {
        province: "",
        city: "",
        county: "",
        provinceOptions: [],
        cityOptions: [],
        countyOptions: [],
      },
      webPort: 0,
      ntpTestLoading: false,
      buttonAuthority: sessionStorage.getItem("buttonAuthoritys") || [],
    };
  },
  created() {
    if (this.isShow("settings:sysInfo")) {
      this.activeName = "sysInfo";
    } else if (this.isShow("settings:timeSet")) {
      this.activeName = "timeSet";
    } else if (this.isShow("settings:cluster")) {
      this.activeName = "cluster";
    } else if (this.isShow("settings:permission")) {
      this.activeName = "permission";
    } else if (this.isShow("settings:broadcast")) {
      this.activeName = "broadcast";
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initSysinfo();
      // this.initAlarmConf();
      this.initResourceConfig();
      this.initClockConf();
      //this.markStartTime();
    });
  },
  beforeDestroy() {
    clearTimeout(this.clockTimer);
    clearInterval(this.browserTimer);
  },
  methods: {
    formatTooltip(v){
      return Math.round(v*1.2)
    },
    isShow(authority) {
      return (
        this.isAdmin || this.buttonAuthority.indexOf("," + authority + ",") > -1
      );
    },
    initSysinfo() {
      this.loadingText = "正在获取设备信息...";
      getDevInfo()
        .then((rsp) => {
          if (rsp && rsp.success) {
            this.sysinfo = rsp.data;
            this.sysinfo.gateway = this.sysinfo.gateway.trim();
            this.originNetConfig.ip = this.sysinfo.ip;
            this.originNetConfig.mask = this.sysinfo.subMask;
            this.originNetConfig.gw = this.sysinfo.gateway.trim();
            this.originNetConfig.dns = this.sysinfo.dns ? this.sysinfo.dns : "";
            // this.alarmConf.min_video_len = rsp.data.min_video_len;
            // this.alarmConf.max_video_len = rsp.data.max_video_len;
            this.fakeObj.min = rsp.data.min_video_len / 1.2
            this.fakeObj.max = rsp.data.max_video_len / 1.2
            if (this.sysinfo.deviceInfo) {
              let devInfo = this.sysinfo.deviceInfo;
              this.sysinfo.cpuInfo = devInfo.cpu[0].modelName;
              this.sysinfo.disks = "( " + devInfo.disk + ") ";
              this.sysinfo.mem =
                (devInfo.mem.total / 1024 / 1024 / 1024).toFixed(2) + "GB";
              // this.sysinfo.arch = devInfo.host.kernelArch;
              this.sysinfo.uptime = this.secondsFormat(devInfo.host.uptime);
            }
            if (!this.sysinfo.server_port) {
              this.sysinfo.server_port = 7003;
            }
            this.webPort = this.sysinfo.server_port;
            this.sysinfo.webVersion = "V" + config.version;
          }
          this.loading = false;
        })
        .catch((err) => {
          this.loading = false;
        });
    },
    secondsFormat(s) {
      var day = Math.floor(s / (24 * 3600)); // Math.floor()向下取整
      var hour = Math.floor((s - day * 24 * 3600) / 3600);
      var minute = Math.floor((s - day * 24 * 3600 - hour * 3600) / 60);
      var second = s - day * 24 * 3600 - hour * 3600 - minute * 60;
      return day + "天" + hour + "时" + minute + "分" + second + "秒";
    },
    initClockConf(ntpTest = false) {
      getClockInfo().then((rsp) => {
        if (rsp && rsp.success) {
          this.timezone = rsp.data.time_zone;
          if (!ntpTest) {
            this.syncType = rsp.data.ntp ? "1" : "2";
          }
          if (rsp.data.ntp) {
            this.ntpServer = rsp.data.ntp_server;
            this.timeInterval = rsp.data.interval;
          }
          this.timestamp = rsp.data.local_time;
          if (this.clockTimer === null) {
            this.runClock();
          }
        }
      });
    },
    runClock() {
      this.equipmentTime = this.formatTime(++this.timestamp, "Y-M-D h:m:s");
      this.clockTimer = setTimeout(() => {
        this.runClock();
      }, 1000);
    },
    initAlarmConf() {
      // getAlarmConfig().then(rsp => {
      //   if (rsp && rsp.success) {
      //     this.alarmConf = rsp.data;
      //   }
      // });
    },
    initResourceConfig() {
      getResourceConfig().then((rsp) => {
        if (rsp && rsp.success) {
          this.ipServer.diyOrLocalIP = rsp.data.ipType;
          this.ipServer.ip = rsp.data.serviceIp;
          this.ipServer.localhost = rsp.data.domain;
          this.ipServer.localFilePort = rsp.data.filePort;
        }
      });
    },
    syncBrowser(enable) {
      if (!enable) {
        clearInterval(this.browserTimer);
      } else {
        this.browserTimer = setInterval(() => {
          let timestamp = new Date().getTime() / 1000;
          this.settime = this.formatTime(timestamp, "Y-M-D h:m:s");
        }, 1000);
      }
    },
    markStartTime() {
      let timestamp = new Date().getTime() / 1000;
      this.settime = this.formatTime(timestamp, "Y-M-D h:m:s");
    },
    submitSysinfo() {
      this.$refs["sysinfo"].validate((valid) => {
        if (valid) {
          if (
            this.sysinfo.ip !== this.originNetConfig.ip ||
            this.sysinfo.subMask !== this.originNetConfig.mask ||
            this.sysinfo.dns !== this.originNetConfig.dns ||
            this.sysinfo.gateway !== this.originNetConfig.gw
          ) {
            if (this.sysinfo.ip !== this.originNetConfig.ip) {
              let newUri =
                location.protocol +
                "//" +
                this.sysinfo.ip +
                ":" +
                this.sysinfo.server_port;
              var changeIPTimer = setTimeout(() => {
                this.$alert(
                  '<strong>您已修改了服务器ip, 请重新登录</strong><a target="_parent" href="' +
                    newUri +
                    '"> ' +
                    newUri +
                    "<a/>",
                  "提示",
                  {
                    dangerouslyUseHTMLString: true,
                  }
                );
              }, 10000);
            }
            this.$confirm("确认需要修改服务器网络配置吗?", {
              center: true,
              cancelButtonClass: "comfirm-class-cancle",
              confirmButtonClass: "comfirm-class-sure",
            })
              .then(() => {
                this.loading = true;
                this.loadingText = "正在处理...";
                saveDevInfo(this.sysinfo)
                  .then((rsp) => {
                    if (rsp && rsp.success) {
                      this.$notify({
                        type: "success",
                        message: "本机信息保存成功",
                      });
                    }
                    this.initSysinfo();
                    this.loading = false;
                  })
                  .catch((err) => {
                    this.loading = false;
                    clearTimeout(changeIPTimer);
                    this.$notify({
                      type: "error",
                      message: "保存失败",
                    });
                  });
              })
              .catch((err) => {});
          } else {
            saveDevInfo(this.sysinfo).then((rsp) => {
              if (rsp && rsp.success) {
                this.$notify({
                  type: "success",
                  message: "本机信息保存成功",
                });
                this.initSysinfo();
              }
            });
            if (this.sysinfo.server_port !== this.webPort) {
              let newUri =
                location.protocol +
                "//" +
                this.sysinfo.ip +
                ":" +
                this.sysinfo.server_port;
              var changeIPTimer = setTimeout(() => {
                this.$alert(
                  '<strong>您已修改了服务器端口, 请重新登录</strong><a target="_parent" href="' +
                    newUri +
                    '"> ' +
                    newUri +
                    "<a/>",
                  "提示",
                  {
                    dangerouslyUseHTMLString: true,
                  }
                );
              }, 5000);
            }
          }
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    checkTimeZone(val) {},
    submitClock() {
      if (this.syncType === "1") {
        if (this.ntpServer === "") {
          this.$notify({
            type: "error",
            message: "NTP 服务器地址不能为空",
          });
          return false;
        } else if (this.timeInterval === "") {
          this.timeInterval = 1;
        }
      } else {
        if (this.settime === "") {
          this.$notify({
            type: "error",
            message: "设置时间不能为空",
          });
          return false;
        }
      }
      let requestBody = {
        timeZone: this.timezone,
        ntp: this.syncType === "1",
        ntpServer: this.ntpServer,
        interval: this.timeInterval,
        newTime: this.settime,
      };
      saveClockInfo(requestBody).then((rsp) => {
        if (rsp && rsp.success) {
          this.$notify({
            type: "success",
            message: "设置成功",
          });
        }
        this.initClockConf();
      });
    },
    testNTP() {
      this.ntpTestLoading = true;
      testNTPserver({ server: this.ntpServer })
        .then((rsp) => {
          if (rsp && rsp.success) {
            this.$notify({
              type: "success",
              message: "时间同步成功",
            });
          } else {
            this.$notify({
              type: "error",
              message: "时间同步失败",
            });
          }
          this.ntpTestLoading = false;
          this.initClockConf(true);
        })
        .catch((err) => {
          this.$notify({
            type: "error",
            message: "时间同步失败,请检查服务器ip",
          });
          this.ntpTestLoading = false;
        });
    },
    submitAlarm() {
      saveAlarmConfig({
        min_video_len: this.min_video_len,
        max_video_len: this.max_video_len,
      }).then((rsp) => {
        if (rsp && rsp.success) {
          this.$notify({
            type: "success",
            message: "保存成功",
          });
        }
      });
    },
    // submitResource() {
    //   let regNum = /^[0-9]*$/;
    //   if (!this.vaildHost(this.ipServer.localhost)) {
    //     this.$notify({
    //       type: "warning",
    //       message: "请输入正确格式得域名!",
    //     });
    //     return false;
    //   }
    //   if (!regNum.test(this.ipServer.localFilePort)) {
    //     this.$notify({
    //       type: "warning",
    //       message: "请输入正确的端口号!",
    //     });
    //     return false;
    //   }
    //   saveResourceConfig({
    //     domain: this.ipServer.localhost,
    //     ipType: this.ipServer.diyOrLocalIP,
    //     serviceIp: this.ipServer.ip,
    //     filePort: Number(this.ipServer.localFilePort),
    //   }).then((res) => {
    //     if (res && res.success) {
    //       this.$notify({
    //         type: "success",
    //         message: "保存成功",
    //       });
    //     } else {
    //       this.$notify({
    //         type: "error",
    //         message: "保存失败",
    //       });
    //     }
    //   });
    // },
    // onIpBlur(e, ip) {
    //   console.log(e, ip);
    // },
    formatTime(number, format) {
      var formateArr = ["Y", "M", "D", "h", "m", "s"];
      var returnArr = [];
      var date = new Date(number * 1000);
      returnArr.push(date.getFullYear());
      returnArr.push(this.formatNumber(date.getMonth() + 1));
      returnArr.push(this.formatNumber(date.getDate()));
      returnArr.push(this.formatNumber(date.getHours()));
      returnArr.push(this.formatNumber(date.getMinutes()));
      returnArr.push(this.formatNumber(date.getSeconds()));
      for (var i in returnArr) {
        format = format.replace(formateArr[i], returnArr[i]);
      }
      return format;
    },
    //数据转化
    formatNumber(n) {
      n = n.toString();
      return n[1] ? n : "0" + n;
    },
    menuOpen(event) {},
    menuClose(event) {},
    // handleEdit(row) {
    //   console.log(row);
    //   row.edit = true;
    // },
    // handleCancel(row) {
    //   row.edit = false;
    //   console.log(row);
    // },
    // handleSave(row) {
    //   console.log(row);
    //   row.edit = false;
    //   this.$notify({
    //     message: "保存成功",
    //     type: "success",
    //   });
    // },
    //校验域名
    vaildHost(str) {
      let re = /^(?=^.{3,255}$)(http(s)?:\/\/)?(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+\.\w+)*$/;
      return re.test(str);
    },
  },
};
</script>
<style lang="scss">
.s-basic-setting {
  width: 100%;
  height: 100%;
  .el-form {
    width: 1000px;
    // margin-left: -80px;
    .el-form-item {
      text-align: left;
      &.is-required:not(.is-no-asterisk) > .el-form-item__label:before {
        margin-left: -8px;
      }
      .el-button {
        float: right;
      }
      .el-form-item__content {
        text-align: left;
        input {
          max-width: 360px;
        }
        .el-date-editor.el-input,
        .el-date-editor.el-input__inner {
          width: 216px;
        }
        .el-checkbox__label {
          padding-left: 5px;
        }
      }
      .el-form-item__label {
        text-align: left;
      }
    }
  }
  .alarmSetting {
    .el-input {
      width: 100%;
      // padding-right: 10px;
    }
    .el-select {
      max-width: 113px;
    }
    .el-slider {
      width: calc(100% - 120px);
      display: inline-block;
      padding-right: 30px;
      box-sizing: border-box;
      vertical-align: middle;
    }
    .el-input-number {
      width: 100px;
      display: inline-block;
      .el-input {
        width: 100%;
      }
    }
  }
  .time-type {
    height: 25px;
    width: 413px;
    line-height: 28px;
    padding: 3px 23px;
    font-size: 14px;
    font-weight: 600;
    background-color: #e4e6ed;
  }
  #cut_min_duration {
    .el-slider__bar {
      background-color: #3d68e1;
    }
    .el-slider__button {
      width: 10px;
      height: 10px;
      border: 4px solid #3d68e1;
    }
  }
  #cut_max_duration {
    .el-slider__bar {
      background-color: #ff9e6e;
    }
    .el-slider__button {
      width: 10px;
      height: 10px;
      border: 4px solid #ff9e6e;
    }
  }
  .menu-css,
  .el-menu {
    border-right: none;
    list-style: none;
    position: relative;
    margin: 0;
    padding-left: 10px;
    background-color: #ffffff;
    .el-submenu__title {
      height: 35px;
      line-height: 35px;
      font-size: 14px;
      color: #303133;
      padding: 0 20px;
      list-style: none;
      cursor: pointer;
      position: relative;
      -webkit-transition: border-color 0.3s, background-color 0.3s, color 0.3s;
      transition: border-color 0.3s, background-color 0.3s, color 0.3s;
      -webkit-box-sizing: border-box;
      box-sizing: border-box;
      white-space: nowrap;
    }
    .tree-font {
      font-family: PingFangSC-Medium;
      font-size: 14px;
      color: #222222;
      text-align: left;
    }
    li {
      text-align: left;
      .el-submenu__title {
        // border-bottom: solid 1px #e6e6e6;
        padding-left: 10px !important;
        background-color: #e4e6ed !important;
        border-radius: 2px;
        .el-submenu__icon-arrow {
          position: absolute;
          top: 50%;
          right: auto;
          left: 135px;
          margin-top: -7px;
          -webkit-transition: -webkit-transform 0.3s;
          transition: -webkit-transform 0.3s;
          transition: transform 0.3s;
          transition: transform 0.3s, -webkit-transform 0.3s;
          font-size: 12px;
        }
      }
    }
  }
  .save-btn {
    text-align: right;
    position: relative;
    right: 40px;
  }
}
</style>
<style lang="scss" scoped>
.flex-box {
  display: flex;
  height: 50px;
  label {
    width: 120px;
  }
  .el-input {
    width: 300px;
  }
}
.desc-info {
  margin-bottom: 14px;
  .flex-box {
    height: 40px;
    line-height: 40px;
    .xiangqin-label {
      width: 80px;
    }
  }
}
.menu-css,
.el-menu {
  border-right: none;
  list-style: none;
  position: relative;
  margin: 0;
  padding-left: 0;
  background-color: #ffffff;
  .tree-font {
    font-family: PingFangSC-Medium;
    font-size: 14px;
    color: #222222;
    text-align: left;
  }
  li {
    text-align: left;
    .el-submenu__title {
      .el-submenu__icon-arrow {
        position: absolute;
        top: 50%;
        right: 0;
        margin-top: -7px;
        -webkit-transition: -webkit-transform 0.3s;
        transition: -webkit-transform 0.3s;
        transition: transform 0.3s;
        transition: transform 0.3s, -webkit-transform 0.3s;
        font-size: 12px;
      }
    }
  }
}
</style>
src/pages/settings/components/CloudNode.vue
@@ -1,7 +1,21 @@
<template>
  <div class="cloud">
    <div class="inner">
      <!-- <div
        class="rect"
        :style="{width:`${minWidth+BaseWidth}px`,height:`${minHeight+BaseHeight}px`}"
      >-->
      <div class="rect">
        <!-- <div
          class="node"
          v-for="item in insideNodes"
          :key="item.id"
          :style="{top:item.t+'px',left:item.l+'px'}"
        >
          <span class="node-icon">
          </span>
          <span class="node-name">{{item.nodeName}}</span>
        </div>-->
        <serfDiagram
          ref="inside-nodes"
          :members="insideNodes"
@@ -24,46 +38,156 @@
        :startX="60"
        class="outer-nodes"
      ></serfDiagram>
      <!-- <div
        class="node"
        v-for="(item,index) in outsideNodes"
        :key="item.id"
        :style="{top:36*(index+1)+'px',left:60*(index+1)+'px'}"
      >
        <span class="node-icon"></span>
        <span class="node-name">{{item.nodeName}}</span>
      </div>-->
    </div>
  </div>
</template>
<script>
import SerfDiagram from "@/components/serfDiagram";
export default {
  name: "cloudNode",
  name: 'cloudNode',
  props: {
    nodes: Array,
    nodes: Array
  },
  components: {
    SerfDiagram,
    SerfDiagram
  },
  data() {
  data () {
    return {
      agentName: "",
      agentName: '',
      nodeIcons: [],
      //insideNodes: [],
      BaseWidth: 150,
      BaseHeight: 70,
      minWidth: 0,
      minHeight: 0,
    };
      mockData: [
        {
          cluster_id: "b6132bfe-d3af-4710-ba89-436f614c2f46",
          hardwareType: "01",
          id: "DSVAD010120190622",
          node_id: "DSVAD010120190622",
          node_ip: "192.168.20.10:30190",
          nodeName: "开发20.10-1",
          role: 'pc'
        },
        {
          cluster_id: "b6132bfe-d3af-4710-ba89-436f614c2f",
          hardwareType: "02",
          id: "DSVAD010120190623",
          node_id: "DSVAD010120190623",
          node_ip: "192.168.20.10:30190",
          nodeName: "开发测试20.11-1",
          role: 'master'
        },
        {
          cluster_id: "b6132bfe-d3af-4710-ba89-436f614c2g",
          hardwareType: "03",
          id: "DSVAD010120190624",
          node_id: "DSVAD010120190624",
          node_ip: "192.168.20.10:30190",
          nodeName: "测试20.13-1",
          role: 'pc'
        },
        {
          cluster_id: "b6132bfe-d3af-4710-ba89-436f614c2h",
          hardwareType: "03",
          id: "DSVAD010120190625",
          node_id: "DSVAD010120190625",
          node_ip: "192.168.20.10:30190",
          nodeName: "测试20.101-1",
          role: 'server'
        },
        {
          cluster_id: "b6132bfe-d3af-4710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD01012019063",
          node_id: "DSVAD01012019063",
          node_ip: "192.168.20.10:30190",
          nodeName: "开发测试20.15-1",
          role: 'master'
        },
        {
          cluster_id: "b6132bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD0101201906",
          node_id: "DSVAD0101209063",
          node_ip: "192.168.20.10:30190",
          nodeName: "k20.15-128437586",
          role: 'master'
        },
        {
          cluster_id: "b612bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD010120906",
          node_id: "DSVAD010120963",
          node_ip: "192.168.20.107:30190",
          nodeName: "kfl20.15-127586",
          role: 'master'
        },
        {
          cluster_id: "b6132bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD0101201906",
          node_id: "DSVAD001209063",
          node_ip: "192.168.20.10:30190",
          nodeName: "k20.15-128437586",
          role: 'master'
        },
        {
          cluster_id: "b612bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD01010906",
          node_id: "DSVAD012063",
          node_ip: "192.168.20.107:30190",
          nodeName: "kfl20.15-127586",
          role: 'master'
        },
        {
          cluster_id: "b6132bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD0101201906",
          node_id: "DSVAD0012063",
          node_ip: "192.168.20.10:30190",
          nodeName: "k20.15-128437586",
          role: 'master'
        },
        {
          cluster_id: "b612bfe-d3af-710-ba89-436f614cf",
          hardwareType: "02",
          id: "DSVAD01010906",
          node_id: "DSVAD01012063",
          node_ip: "192.168.20.107:30190",
          nodeName: "kfl20.15-127586",
          role: 'master'
        },
      ]
    }
  },
  mounted() {
    console.log(this.nodes);
  mounted () {
    console.log(this.nodes)
    //this.getInsideNodes();
  },
  methods: {
    getRandom(index) {
    getRandom (index) {
      if (index % 2 == 0) {
        return Math.random() * 20;
        return Math.random() * 20
      } else {
        return Math.random() * 50;
        return Math.random() * 50
      }
    },
    getInsideNodes() {
      let arr = this.nodes.filter(
        (item) => item.hardwareType == "01" || item.hardwareType == "02"
      );
    getInsideNodes () {
      //return this.nodes.filter(item=>item.hardwareType=='01'||item.hardwareType=='02');
      //return this.mockData.filter(item => item.hardwareType == '01' || item.hardwareType == '02');
      let arr = this.nodes.filter(item => item.hardwareType == '01' || item.hardwareType == '02');
      let len = arr.length;
      let lefts = [];
      let tops = [];
@@ -75,55 +199,69 @@
          t: 30 * (index + 1),
          nodeName: item.nodeName,
          id: item.id,
          workType: item.workType,
        };
          workType: item.workType
        }
      });
      this.minWidth = Math.max(...lefts) - Math.min(...lefts);
      this.minHeight = Math.max(...tops) - Math.min(...tops);
      console.log("w,h", this.minWidth, this.minHeight);
      console.log('w,h', this.minWidth, this.minHeight);
    },
  },
  computed: {
    cloudPic() {
      return "/images/settings/cloud.png";
    cloudPic () {
      return '/images/settings/cloud.png'
    },
    insideNodes() {
      return this.nodes.filter(
        (item) => item.hardwareType == "01" || item.hardwareType == "02"
      );
    insideNodes () {
      return this.nodes.filter(item => item.hardwareType == '01' || item.hardwareType == '02');
    },
    insideSizeX() {
      return 160 + 200 * 0.2 * this.insideNodes.length <= 400
        ? 160 + 200 * 0.2 * this.insideNodes.length
        : 400;
    insideSizeX () {
      return 160 + 200 * 0.2 * (this.insideNodes.length) <= 400 ? 160 + 200 * 0.2 * (this.insideNodes.length) : 400;
    },
    insideSizeY() {
      return 140 + 200 * 0.2 * this.insideNodes.length <= 380
        ? 140 + 200 * 0.2 * this.insideNodes.length
        : 380;
    insideSizeY () {
      return 140 + 200 * 0.2 * (this.insideNodes.length) <= 380 ? 140 + 200 * 0.2 * (this.insideNodes.length) : 380;
    },
    insideStartX() {
      return this.insideSizeX / 3;
    insideStartX () {
      return this.insideSizeX / 3
    },
    outsideNodes() {
      return this.nodes.filter((item) => item.hardwareType == "03");
    },
  },
};
    // insideNodes () {
    //   //return this.nodes.filter(item=>item.hardwareType=='01'||item.hardwareType=='02');
    //   //return this.mockData.filter(item => item.hardwareType == '01' || item.hardwareType == '02');
    //   let arr = this.mockData.filter(item => item.hardwareType == '01' || item.hardwareType == '02');
    //   let len = arr.length;
    //   let lefts = [];
    //   let tops = [];
    //   let temp = arr.map((item,index)=>{
    //     lefts.push((20-len)*(index+1));
    //     tops.push(30*(index+1));
    //     return {
    //       l: (20-len)*(index+1),
    //       t: 30*(index+1),
    //       nodeName: item.nodeName,
    //       id: item.id,
    //       workType: item.workType
    //     }
    //   });
    //   return temp;
    // },
    outsideNodes () {
      return this.nodes.filter(item=>item.hardwareType=='03');
      //return this.mockData.filter(item => item.hardwareType == '03');
    }
  }
}
</script>
<style lang="scss">
.cloud {
  width: 100%;
  display: flex;
  .inner {
    background: url("/images/settings/easy-cloud.png") no-repeat;
    background: url('/images/settings/easy-cloud.png') no-repeat;
    background-size: 100%;
    margin-top: -55px;
    margin-left: 55px;
    margin-top: -80px;
    .rect {
      //background: rgba(176, 203, 253, 0.3);
      position: relative;
      margin: 130px 100px 70px;
      margin: 130px 100px 100px;
      .node {
        position: absolute;
        .node-icon {
src/pages/settings/components/ClusterManagement.vue
New file
@@ -0,0 +1,885 @@
<template>
  <div class="s-cluster-management">
    <div class="ui-top-view">
      <div class="ui-top-title">视频分析集群管理</div>
    </div>
    <el-row>
      <el-col :span="12">
        <el-tabs v-model="activeName" id="e-alaycluster" v-if="!isHasColony">
          <el-tab-pane label="创建集群" name="1" :disabled="isHasColony">
            <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px">
              <el-form-item label="集群名称" prop="clustername">
                <el-input v-model="ruleForm.clustername" placeholder="手动输入, 如“集群A”" size="small"></el-input>
              </el-form-item>
              <el-form-item label="集群ID">
                <el-input v-model="clusterid" placeholder="不允许输入,保存后回显" disabled size="small"></el-input>
              </el-form-item>
              <el-form-item label="集群密码" prop="clusterpwd">
                <el-input v-model="ruleForm.clusterpwd" placeholder="请输入6位密码,或点击生成" size="small">
                  <el-button type="text" slot="suffix" @click="generatePassword">生成密码</el-button>
                </el-input>
              </el-form-item>
              <el-form-item label="集群IP" prop="virtualIp">
                <ip-input :ip="ruleForm.virtualIp" :on-blur="onIpBlur" class="ip-input-comp"></ip-input>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" size="small" @click="submitForm('ruleForm')">保存</el-button>
              </el-form-item>
            </el-form>
          </el-tab-pane>
          <!-- 加入已有集群 -->
          <el-tab-pane label="加入已有集群" name="2" :disabled="isHasColony">
            <el-form
              label-width="80px"
              :model="joinForm"
              :rules="joinRules"
              ref="joinForm"
              class="join-form"
              v-loading="joinLoading"
            >
              <el-form-item label="集群ID" style="width:440px" prop="clusterid">
                <el-input v-model="joinForm.clusterid" placeholder size="small"></el-input>
              </el-form-item>
              <el-form-item label="IP地址" style="width:440px" prop="clusterip">
                <el-input
                  v-model="joinForm.clusterip"
                  placeholder="请输入集群内任意IP地址"
                  size="small"
                  autocomplete="new-password"
                ></el-input>
                <!-- <el-button size="mini" :disabled="searchDis" @click="searchColony">搜索集群</el-button>
                <el-button size="mini" @click="stopSearch">停止搜索</el-button>-->
              </el-form-item>
              <el-form-item label="集群密码" prop="clusterpwd" style="width:440px">
                <el-input
                  v-model="joinForm.clusterpwd"
                  placeholder="请输入集群密码"
                  show-password
                  size="small"
                  autocomplete="new-password"
                >
                  <el-button
                    type="text"
                    slot="suffix"
                    v-show="!searchDis"
                    @click="searchColony"
                  >搜索集群</el-button>
                  <el-button type="text" slot="suffix" v-show="searchDis" @click="stopSearch">
                    <i class="el-icon-loading"></i>停止搜索
                  </el-button>
                </el-input>
              </el-form-item>
              <el-form-item style="width:440px">
                <el-button type="primary" @click="join('joinForm')" size="small">加入集群</el-button>
              </el-form-item>
              <div class="form-tip">请输入以上信息加入集群,或者通过集群密码搜索后进行加入</div>
            </el-form>
          </el-tab-pane>
        </el-tabs>
        <!-- 有集群的情况 -->
        <div v-if="isHasColony" id="h-alaycluster">
          <el-form :model="ruleForm" ref="ruleForm" label-width="100px">
            <el-form-item label="集群名称" prop="clustername">
              <el-input v-model="ruleForm.clustername" placeholder="手动输入, 如“集群A”" size="small"></el-input>
            </el-form-item>
            <el-form-item label="集群ID">
              <el-input v-model="clusterid" placeholder="不允许输入,保存后回显" disabled size="small"></el-input>
            </el-form-item>
            <el-form-item label="集群密码" prop="clusterpwd">
              <el-input
                v-model="ruleForm.clusterpwd"
                disabled
                placeholder="请输入6位密码,或点击生成"
                size="small"
              ></el-input>
            </el-form-item>
            <el-form-item label="集群IP" prop="virtualIp">
              <ip-input :ip="ruleForm.virtualIp" :on-blur="onIpBlur" class="ip-input-comp"></ip-input>
            </el-form-item>
            <el-form-item style="text-align: right;">
              <el-button size="small" type="danger" @click="leave">退出集群</el-button>
              <el-button
                style="margin-right:10px;"
                type="primary"
                size="small"
                @click="updateCluster('manageForm')"
              >保存</el-button>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
      <el-col
        :span="12"
        style="height: 100%;"
        v-if="members.length !== 0 && isSearch"
        class="node-container"
      >
        <serfDiagram
          ref="diagram"
          :members="members"
          :agent="agentName"
          v-loading="loading"
          :isShowHover="false"
          @selected-node="joinNode"
          class="nodes-svg"
        ></serfDiagram>
      </el-col>
      <el-col
        :span="12"
        style="height: 100%;"
        v-if="innerNodes.length !== 0 && !isSearch"
        class="node-container"
      >
        <cloud-node :nodes="innerNodes"></cloud-node>
      </el-col>
    </el-row>
    <!-- <div class="ui-top-view">
      <div class="ui-top-title">存储集群管理</div>
    </div>
    <el-row>
      <el-col :span="12">
        <el-tabs id="e-dbcluster" v-model="sActiveName">
          <el-tab-pane label="创建集群" name="s-first" v-if="sActiveName != 's-third1'">
            <el-button
              type="primary"
              style="float: left;margin: 20px 0px;"
              size="small"
              @click="createEsCluster()"
            >创建存储集群</el-button>
          </el-tab-pane>
          <el-tab-pane label="加入已有集群" name="s-second" v-if="sActiveName != 's-third1'">
            <el-form label-width="80px">
              <el-form-item label="IP地址" style="text-align: left;width: 300px;">
                <el-input
                  v-model="esNodeIp"
                  placeholder="请输入集群内任意IP地址"
                  size="small"
                  autocomplete="off"
                ></el-input>
                <el-checkbox
                  label="主节点"
                  v-model="esNodeType"
                  style="margin-left: 20px;position: absolute;"
                ></el-checkbox>
              </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  @click="joinESCluster()"
                  size="small"
                  style="float: left;"
                >加入集群</el-button>
              </el-form-item>
            </el-form>
          </el-tab-pane>
          <el-tab-pane label="集群信息" name="s-third">
            <el-table :data="esNodes" style="width: 100%">
              <el-table-column prop="nodeType" label="节点类型"></el-table-column>
              <el-table-column prop="name" label="节点名称"></el-table-column>
              <el-table-column prop="ip" label="节点IP地址" min-width="90px"></el-table-column>
              <el-table-column prop="buildDate" label="注册时间" min-width="120px"></el-table-column>
            </el-table>
          </el-tab-pane>
        </el-tabs>
      </el-col>
    </el-row>-->
  </div>
</template>
<script>
import {
  createSerfCluster,
  randomPwd,
  search,
  getSearchNodes,
  stopSearching,
  findCluster,
  updateClusterName,
  joinCluster,
  leave,
  getVrrp,
  setVrrp,
  createESNode,
  addESNode,
  getEsClusterInfo
} from "@/api/clusterManage";
import {
  getDevInfo
} from "@/api/system";
import cloudNode from "./CloudNode";
import serfDiagram from "@/components/serfDiagram";
import ipInput from "@/components/subComponents/IPInput";
import { isIPv4 } from "@/scripts/validate";
export default {
  components: {
    serfDiagram,
    ipInput,
    cloudNode
  },
  data() {
    const checkPwd = (rule, value, callback) => {
      if (!value) {
        return callback(new Error("密码不能为空"));
      }
      setTimeout(() => {
        if (value.length != 6) {
          callback(new Error("密码应为6位!"));
        } else {
          callback();
        }
      }, 1000);
    };
    return {
      activeName: "1",
      sActiveName: "s-first",
      clusterid: "",
      esNodeIp: "",
      esNodeType: "",
      clusterpwd2: "",
      sClusterip: "",
      ruleForm: {
        clustername: "",
        clusterpwd: "",
        virtualIp: ""
      },
      vrIpForm: {
        enable: true,
        //serve_port: "",
        serve_port: null,
        virtual_ip: ""
      },
      manageForm: {
        clustername: "测试集群1",
        clusterpwd: "123456",
        virtualip: "192.168.1.188"
      },
      joinForm: {
        clusterid: "",
        clusterip: "",
        clusterpwd: ""
      },
      rules: {
        clustername: [
          { required: true, message: "请输入集群名称", trigger: "change" }
        ],
        clusterpwd: [{ validator: checkPwd, trigger: "change" }],
        virtualIp: [
          { required: true, validator: isIPv4, trigger: "change" }
        ]
      },
      // vrIpRules: {
      //   virtualIp: [
      //     { required: true, message: "请输入虚拟IP", trigger: "change" }
      //   ]
      // },
      joinRules: {
        clusterid: [
          { required: true, message: "请输入集群ID", trigger: "change" },
        ],
        clusterip: [
          { required: true, validator: isIPv4, trigger: "change" }
        ],
        clusterpwd: [
          { validator: checkPwd, trigger: "change" }
        ]
      },
      esNodes: [],
      scheduleId: "",
      isHasColony: false,
      isSearch: false,
      currentCluster: {},
      searchNum: "",
      loading: false,
      searchDis: false,
      agentName: "",
      members: [],
      innerNodes: [],
      intervalTimer: null,
      joinLoading: false,
      showJoinConfirm: false
    };
  },
  mounted() {
    this.findCluster();
    let _this = this;
    this.intervalTimer = setInterval(() => {
      _this.findCluster();
    }, 30000);
    //this.getEsClusterNodes();
  },
  beforeDestroy() {
    clearInterval(this.intervalTimer);
  },
  methods: {
    checkPsd(psd) {
      return psd.trim().length === 6
    },
    // cleanValue() {
    //   this.members = [];
    // },
    // sHandleClick(tab, event) {
    //   console.log(tab, event);
    // },
    submitForm(formName) {
      this.$refs[formName].validate(valid => {
        if (valid) {
          //alert("submit!");
          let json = {
            clusterId: this.clusterid,
            clusterName: this.ruleForm.clustername,
            password: this.ruleForm.clusterpwd,
            virtualIp: this.ruleForm.virtualIp
          };
          this.createCluster(json).then(() => {
            this.findCluster();
          });
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    // saveForm(formName) {
    //   this.$refs[formName].validate(valid => {
    //     if (valid) {
    //       alert("submit!");
    //       let json = {
    //         enable: this.vrIpForm.enable,
    //         serve_port: this.vrIpForm.serve_port,
    //         virtual_ip: this.vrIpForm.virtual_ip,
    //       };
    //       setVrrp(json).then(() => {
    //         this.getVrrpInfo();
    //       });
    //     } else {
    //       console.log("error submit!!");
    //       return false;
    //     }
    //   });
    // },
    join(formName) {
      let _this = this;
      this.$refs[formName].validate(valid => {
        if (valid) {
          _this.joinLoading = true;
          // if (Object.keys(this.currentCluster).length === 0) {
          //   this.$notify({
          //     type: "info",
          //     duration: 1000,
          //     message: "请先选择一个集群节点"
          //   });
          //   _this.joinLoading = false;
          //   return true;
          // }
          let nodeIps = this.members.map(i => {
            return i.Address;
          });
          let json = {
            //clusterId: this.currentCluster.cluster_id,
            clusterId: this.joinForm.clusterid,
            password: this.joinForm.clusterpwd,
            nodeIps: [this.joinForm.clusterip]
          };
          this.joinCluster(json).then(() => {
            _this.joinLoading = false;
            this.findCluster();
          }).catch(e => {
            console.log(e);
            _this.joinLoading = false;
          });
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    async createCluster(json) {
      let res = await createSerfCluster(json);
      console.log(res, "创建集群");
      this.$notify({
        title: res.success ? "成功" : "失败",
        message: res.msg,
        type: res.success ? "success" : "error"
      });
    },
    async randomPwd() {
      let res = await randomPwd();
      if (res && res.success) {
        this.ruleForm.clusterpwd = res.data;
      }
    },
    async searchColony() {
      this.isSearch = true;
      this.$refs["joinForm"].clearValidate();
      this.$refs["joinForm"].validateField('clusterpwd');
      if (this.checkPsd(this.joinForm.clusterpwd)) {
        this.members = [];
        let json = {
          password: this.joinForm.clusterpwd,
          //ip: this.joinForm.clusterip
        };
        this.search(json)
          .then(() => {
            this.setSchedule();
            // this.searchDis = false;
            // this.loading = false;
          })
          .catch(() => {
            this.searchDis = false;
            this.loading = false;
            this.isSearch = false;
          });
      } else {
        this.searchDis = false;
        this.loading = false;
        this.isSearch = false;
        return false;
      }
      //});
    },
    async search(json) {
      let res = await search(json);
      if (res && res.success) {
        console.log(res, "搜索集群");
        this.searchNum = res.data;
      }
      this.searchDis = true;
      this.loading = true;
      window.setTimeout(() => {
        this.stopSearch();
      }, 10 * 1000);
    },
    //搜索集群
    async getSearchNodes() {
      let res = await getSearchNodes();
      if (res && res.success) {
        let list = res.data.map(i => {
          let obj = {};
          obj.cluster_id = i.clusterID ? i.clusterID : "";
          obj.create_time = i.create_time ? i.create_time : "";
          obj.id = i.nodeID ? i.nodeID : "";
          obj.node_id = i.nodeID ? i.nodeID : "";
          obj.Address = i.nodeAddress ? i.nodeAddress : "";
          obj.nodeName = i.nodeAddress ? i.nodeAddress : "";
          obj.role = i.role ? i.role : "pc";
          return obj;
        });
        list.map(i => {
          let found = this.members.find(element => {
            return element.node_id === i.node_id;
          });
          if (found === undefined) {
            this.members.push(i);
          }
        });
      }
    },
    setSchedule() {
      this.scheduleId = window.setInterval(() => {
        this.getSearchNodes();
      }, 1000);
    },
    async stopSearch() {
      if (!this.loading) {
        return true;
      }
      stopSearching({
        searchNum: this.searchNum
      }).then((res) => {
        console.log(res, '正常结束')
        this.loading = false;
        this.searchDis = false;
        window.clearInterval(this.scheduleId);
      }).catch((err) => {
        console.log(err, '报错结束')
        this.$notify({
          type: 'error',
          duration: 1000,
          message: '停止搜索报错!'
        })
        // window.setTimeout(()=>{
        //   this.loading = false;
        //   this.searchDis = false;
        //   window.clearInterval(this.scheduleId);
        // },2000)
      })
    },
    async findCluster() {
      let res = await findCluster();
      if (res && res.success) {
        if (res.data && res.data.clusterId) {
          this.isHasColony = true;
          this.activeName = "3";
          this.clusterid = res.data.clusterId;
          this.ruleForm.clustername = res.data.clusterName;
          this.ruleForm.clusterpwd = '******';
          this.ruleForm.virtualIp = res.data.virtualIp
          //let list = res.data.nodes.map(i => {
          this.isSearch = false;
          this.innerNodes = res.data.nodes.map(i => {
            let obj = {};
            obj.device_type = i.device_type;
            obj.workType = i.device_type.substr(2, 2);
            obj.hardwareType = i.device_type.substr(4, 2);
            obj.cluster_id = i.cluster_id;
            obj.clusterName = res.data.clusterName;
            obj.create_time = i.create_time;
            obj.id = i.id;
            obj.node_id = i.node_id;
            obj.node_ip = i.node_ip;
            obj.nodeName = i.node_name;
            obj.Address = i.node_ip;
            obj.role = i.drift_state ? i.drift_state : "pc";
            return obj;
          });
          //this.members = this.members.concat(list);
          console.log(this.members)
        } else {
          this.isHasColony = false;
          // this.activeName = '1'
        }
      }
    },
    // getVrrpInfo() {
    //   getVrrp().then(res => {
    //     if (res.success) {
    //       this.vrIpForm.virtual_ip = res.data.virtual_ip;
    //       this.vrIpForm.enable = res.data.enable;
    //     }
    //   }).catch(e => {
    //     console.log(e)
    //   })
    // },
    async updateCluster() {
      if (this.ruleForm.clustername === "") {
        this.$message({
          type: "error",
          message: "集群名称不能为空"
        })
        return
      }
      let res = await updateClusterName({
        clusterName: this.ruleForm.clustername,
        virtualIp: this.ruleForm.virtualIp
      });
      this.$notify({
        title: res.success ? "成功" : "失败",
        message: res.msg,
        type: res.success ? "success" : "error"
      });
    },
    async joinCluster(json) {
      let res = await joinCluster(json);
      if (res.success) {
        this.members = []
      }
      this.$notify({
        title: res.success ? "成功" : "失败",
        message: res.msg,
        type: res.success ? "success" : "error"
      });
    },
    leave() {
      this.$confirm(`确定退出集群吗?`, {
        center: true,
        cancelButtonClass: "comfirm-class-cancle",
        confirmButtonClass: "comfirm-class-sure"
      }).then(async () => {
        let res = await leave();
        this.$notify({
          title: res.success ? "成功" : "失败",
          message: res.msg,
          type: res.success ? "success" : "error"
        });
        if (res && res.success) {
          this.ruleForm.clustername = "";
          this.ruleForm.clusterpwd = ""
          this.clusterid = "";
          this.isHasColony = false;
          this.activeName = "1";
          this.members = [];
          this.innerNodes = [];
        }
      }).catch(() => {
         this.ruleForm.clustername = "";
          this.ruleForm.clusterpwd = ""
          this.clusterid = "";
          this.isHasColony = false;
          this.activeName = "1";
          this.members = [];
          this.innerNodes = [];
       });
    },
    joinNode(event, node) {
      console.log('join', node)
      let _this = this;
      this.currentCluster.cluster_id = node.cluster_id;
      if (this.activeName === "3") {
        this.manageForm.clustername = node.clusterName;
        this.clusterid = node.cluster_id;
        return;
      }
      if (this.activeName === "2") {
        if (_this.showJoinConfirm) return;
        this.$confirm("是否要加入节点 " + node.nodeName + "?", "加入集群", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "success"
        })
          .then(() => {
            _this.showJoinConfirm = true;
            let json = {
              //clusterId: this.currentCluster.cluster_id,
              clusterId: node.cluster_id,
              password: this.joinForm.clusterpwd,
              nodeIps: [node.Address]
            };
            this.joinCluster(json).then(() => {
              _this.showJoinConfirm = false;
              this.findCluster();
            }).catch(e => {
              console.log(e);
              _this.showJoinConfirm = false;
            });
          }).catch(e => {
            console.log(e)
          });
        // this.$refs["joinForm"].validate(valid => {
        //   if (valid) {
        //     if (_this.showJoinConfirm) return;
        //     _this.showJoinConfirm = true;
        //     this.$confirm("是否要加入节点 " + node.nodeName + "?", "加入集群", {
        //       confirmButtonText: "确定",
        //       cancelButtonText: "取消",
        //       type: "success"
        //     })
        //       .then(() => {
        //         console.log(this.currentCluster, '选择的集群节点')
        //         this.join("joinForm");
        //         _this.showJoinConfirm = false;
        //       })
        //       .catch(() => {
        //         this.$notify({
        //           type: "info",
        //           duration: 1000,
        //           message: "已取消"
        //         });
        //         _this.showJoinConfirm = false;
        //       });
        //   } else {
        //     console.log("error submit!!");
        //     return false;
        //   }
        // });
      }
    },
    generatePassword() {
      var chars =
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
      var uuid = [];
      for (let i = 0; i < 6; i++) {
        uuid[i] = chars[0 | (Math.random() * 50)];
      }
      this.ruleForm.clusterpwd = uuid.join("");
    },
    async getEsClusterNodes() {
      let rsp = await getDevInfo();
      let hostIpAddr = "";
      if (rsp && rsp.success) {
        hostIpAddr = rsp.data.ip;
      }
      if (hostIpAddr.length) {
        rsp = await getEsClusterInfo({ ip: hostIpAddr });
        if (rsp && rsp.success) {
          this.esNodes = rsp.data.map(el => {
            return {
              // buildDate: "2018-06-11T23:38:03.357887Z",
              buildDate: el.buildDate.split("T")[0] + " " + el.buildDate.split("T")[1].slice(0, 8),
              ip: el.ip,
              name: el.name,
              nodeType: el.nodeType,
            }
          })
          if (this.esNodes.length) {
            this.sActiveName = 's-third';
          }
        }
      }
    },
    createEsCluster() {
      createESNode().then(rsp => {
        if (rsp && rsp.success) {
          this.$message({
            type: "success",
            duration: 2000,
            message: "创建成功"
          });
          this.getSearchNodes();
        } else {
          this.$message({
            type: "error",
            duration: 2000,
            message: rsp.msg
          });
        }
      }).catch(rsp => {
        this.$message({
          type: "error",
          duration: 2000,
          message: rsp.msg
        });
      })
    },
    joinESCluster() {
      if (!this.esNodeIp.length) {
        this.$message({
          type: "error",
          duration: 2000,
          message: "请输入正确的ip地址"
        });
        return;
      }
      addESNode({ ip: this.esNodeIp, option: this.esNodeType ? "1" : "2" }).then(rsp => {
        if (rsp && rsp.success) {
          this.$message({
            type: "success",
            duration: 2000,
            message: "加入成功"
          });
          this.getSearchNodes();
        } else {
          this.$message({
            type: "error",
            duration: 2000,
            message: rsp.msg
          });
        }
      }).catch(rsp => {
        this.$message({
          type: "error",
          duration: 2000,
          message: rsp.msg
        });
      })
    },
    onIpBlur(ip) {
      //this.vrIpForm.virtual_ip = ip;
      this.ruleForm.virtualIp = ip;
      console.log(this.ruleForm.virtualIp)
    }
  },
  created() { }
};
</script>
<style lang="scss">
.s-cluster-management {
  width: 100%;
  height: 100%;
  overflow: auto;
  .el-form-item {
    width: 500px;
  }
  .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label:before,
  .el-form-item.is-required:not(.is-no-asterisk)
    .el-form-item__label-wrap
    > .el-form-item__label:before {
    display: none;
  }
  .ip-input-comp {
    width: 400px;
    max-width: 400px;
  }
  .el-button {
    float: right;
  }
  .el-form-item__content {
    text-align: left;
    input {
      max-width: 400px !important;
    }
  }
  #e-alaycluster,
  #e-dbcluster {
    .el-tabs__header {
      border: 0px solid #dcdfe6;
      .el-tabs__item {
        padding: 5px 50px;
        height: 50px;
        font-family: PingFangSC-Regular;
        font-size: 14px;
        color: #222222;
        text-align: center;
        border: 0px solid transparent;
      }
      .el-tabs__item:nth-child(2) {
        padding-left: 50px;
      }
      .el-tabs__item:last-child {
        padding-right: 50px;
      }
      .el-tabs__item.is-active {
        color: #ff7733;
        font-weight: bold;
        // border-right-color: #fff;
        // border-left-color: #fff;
      }
      .el-tabs__item:not(.is-disabled):hover {
        color: #ff7733;
      }
    }
    .el-tabs__active-bar {
      background-color: #ff7733;
    }
    .el-form-item__content {
      text-align: left;
      input {
        max-width: 420px;
      }
    }
  }
  #h-alaycluster {
    .el-form-item__content {
      text-align: left;
      // input {
      //   max-width: 360px;
      // }
    }
  }
}
.ui-top-view {
  height: 30px;
  line-height: 30px;
}
</style>
src/pages/settings/components/LogManagement.vue
New file
@@ -0,0 +1,190 @@
<template>
  <div class="s-log-management">
    <div class="top">
      <b>日志类型:</b>
      <el-select v-model="logValue" placeholder="请选择" size="small">
        <el-option
          v-for="item in logOptions"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        ></el-option>
      </el-select>
      <b>时间:</b>
      <el-date-picker
        v-model="timeValue"
        type="datetimerange"
        size="small"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
      ></el-date-picker>
      <el-input
        v-model="searchValue"
        placeholder="请输入内容"
        clearable
        style="width: 150px;margin: 0px 10px;"
        size="small"
      ></el-input>
      <el-button type="primary" size="small">搜索</el-button>
      <el-button type="danger" size="small" @click="delSelected">批量删除</el-button>
      <el-button type="text" size="small" style="font-size: 13px;font-weight: 600;">导出</el-button>
    </div>
    <div class="foot-table s-table">
      <el-table
        ref="multipleTable"
        highlight-current-row
        :data="tableData"
        style="width: 100%"
        :header-cell-style="{background:'#f8f8f8',color:'#222222'}"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column :align="'center'" sortable prop="index" label="序号"></el-table-column>
        <el-table-column :align="'center'" sortable prop="logtype" label="日志类型"></el-table-column>
        <el-table-column :align="'center'" sortable prop="username" label="用户名"></el-table-column>
        <el-table-column :align="'center'" sortable prop="ipaddress" label="IP地址"></el-table-column>
        <el-table-column :align="'center'" sortable prop="operation" label="操作功能"></el-table-column>
        <el-table-column :align="'center'" sortable prop="operatetime" label="操作时间"></el-table-column>
        <el-table-column :align="'center'" sortable prop="operateinfo" label="操作信息"></el-table-column>
        <el-table-column label="操作" :align="'center'">
          <template slot-scope="scope">
            <el-button
              type="text"
              style="color: red;font-size:16px"
              @click="handleDelete(scope.$index, scope.row)"
              icon="el-icon-delete"
            ></el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tableData: [
        {
          index: "1",
          logtype: "异常",
          username: "admin",
          ipaddress: "192.168.10.110",
          operation: "添加摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        },
        {
          index: "2",
          logtype: "信息",
          username: "admin",
          ipaddress: "192.168.10.108",
          operation: "删除摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        },
        {
          index: "3",
          logtype: "操作",
          username: "admin",
          ipaddress: "192.168.10.110",
          operation: "添加摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        },
        {
          index: "4",
          logtype: "异常",
          username: "admin",
          ipaddress: "192.168.10.110",
          operation: "添加摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        },
        {
          index: "5",
          logtype: "异常",
          username: "admin",
          ipaddress: "192.168.10.110",
          operation: "删除摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        },
        {
          index: "6",
          logtype: "异常",
          username: "admin",
          ipaddress: "192.168.10.110",
          operation: "添加摄像机",
          operatetime: "2019-5-31 16:38:21",
          operateinfo: "添加操作"
        }
      ],
      multipleSelection: [],
      logOptions: [
        {
          value: "全部类型",
          label: "全部类型"
        },
        {
          value: "其他类型",
          label: "其他类型"
        }
      ],
      logValue: "全部类型",
      timeValue: [
        new Date(2000, 10, 10, 10, 10),
        new Date(2000, 10, 11, 10, 10)
      ],
      searchValue: ""
    };
  },
  methods: {
    handleDelete(index, row) {
      console.log(index, row);
    },
    handleSelectionChange(val) {
      this.multipleSelection = val;
    },
    delSelected() {
      console.log(this.multipleSelection);
    }
  }
};
</script>
<style lang="scss">
.s-log-management {
  height: 100%;
  width: 100%;
  .top {
    width: 100%;
    margin-top: 10px;
    margin-bottom: 20px;
    overflow-y: auto;
    min-width: 1156px;
    height: 40px;
    text-align: left;
    b {
      padding: 0px 10px;
    }
  }
  .export {
    display: inline-block;
    padding-right: 10px;
    box-sizing: border-box;
    margin-top: 20px;
    b:hover {
      color: #2249b4;
    }
  }
  .clear-searching {
    cursor: pointer;
    text-decoration: underline;
    width: 40px;
    font-size: 13px;
    color: #3d68e1;
  }
}
</style>
src/pages/settings/components/RadioSet.vue
New file
@@ -0,0 +1,189 @@
<template>
  <div class="s-radio-set">
    <div class="add-btn">
      <el-button size="mini" type="primary" @click="handleAdd()">添加</el-button>
    </div>
    <el-table
      border
      highlight-current-row
      :data="tableData"
      style="width: 100%; margin-top:40px; color:#000"
      :header-cell-style="{background:'#f8f8f8',color:'#222222'}"
    >
      <el-table-column align="center" type="index" label="序号" width="100px"></el-table-column>
      <el-table-column :align="'center'" label="广播名称">
        <template slot-scope="{row}">
          <el-input v-if="row.edit" :autofocus="row.edit" v-model="row.radiosName" size="small" />
          <span v-else>{{ row.radiosName }}</span>
        </template>
      </el-table-column>
      <el-table-column :align="'center'" label="IP地址">
        <template slot-scope="{row}">
          <el-input v-if="row.edit" v-model="row.ipAddress" size="small" />
          <span v-else>{{ row.ipAddress }}</span>
        </template>
      </el-table-column>
      <el-table-column :align="'center'" label="连接测试">
        <template slot-scope="{row}">
          <i v-show="row.isCon" class="el-icon-success" style="color:green; font-size:18px"></i>
          <el-button type="text" @click="handleTest(row)">连接测试</el-button>
        </template>
      </el-table-column>
      <el-table-column label="操作" :align="'center'">
        <template slot-scope="scope">
          <template v-if="scope.row.edit">
            <el-button size="mini" type="info" @click="handleCancel(scope.row)">取消</el-button>
            <el-button size="mini" type="primary" @click="handleSave(scope.row)">保存</el-button>
          </template>
          <template v-else>
            <el-button
              type="text"
              style="color: black;font-size:18px"
              @click="handleEdit(scope.row)"
              icon="el-icon-edit"
            ></el-button>
            <el-button
              type="text"
              style="color: red;font-size:18px"
              @click="handleDelete(scope.$index)"
              icon="el-icon-delete"
            ></el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>
<script>
export default {
  filters: {
    isCon(r) {
      return r.isCon ? r.isCon : false
    }
  },
  data() {
    return {
      radioName: "",
      ipAddress: "",
      tableData: [
        {
          index: "1",
          radiosName: "操场",
          ipAddress: "192.168.1.101",
          edit: false,
          isCon: false
        },
        {
          edit: false,
          index: "2",
          radiosName: "教室",
          ipAddress: "192.168.12.61",
          isCon: false
        },
        {
          edit: false,
          index: "3",
          radiosName: "保安室",
          ipAddress: "192.168.13.121",
          isCon: false
        }
      ]
    };
  },
  mounted() {
    this.testAll()
  },
  methods: {
    testAll() {
      this.tableData.forEach(l => {
        this.$set(l, "isCon", false)
      })
    },
    handleEdit(row) {
      console.log(row);
      row.edit = true;
    },
    handleCancel(row) {
      row.edit = false;
      console.log(row);
    },
    handleDelete(index) {
      this.$confirm("确认删除该广播吗?", {
        center: true,
        cancelButtonClass: "comfirm-class-cancle",
        confirmButtonClass: "comfirm-class-sure"
      })
        .then(() => {
          this.tableData.splice(index, 1)
          this.$notify({
            type: "success",
            message: "删除成功!"
          });
        })
        .catch(() => { });
    },
    handleSave(row) {
      console.log(row);
      row.edit = false;
      this.$notify({
        message: "保存成功",
        type: "success"
      });
    },
    handleTest(row) {
      this.$set(row, 'isCon', true)
    },
    handleAdd() {
      this.tableData.push({
        radiosName: "",
        ipAddress: "",
        edit: true,
        isCon: false
      })
    }
  }
};
</script>
<style lang="scss">
.s-radio-set {
  width: 100%;
  height: 100%;
  .el-dialog {
    border-radius: 8px;
    border: 1px solid #ccc;
    .el-dialog__header {
      border-bottom: 1px solid #ccc;
    }
  }
  .add-btn {
    float: right;
    margin-bottom: 7px;
  }
}
.e-message {
  width: 331px;
}
.e-confirm {
  border-color: #ff0000 !important;
  background-color: #ff0000 !important;
}
.e-confirm:hover {
  border-color: #f83131d6 !important;
  background-color: #f83131d6 !important;
}
.e-cancel {
  border-color: #eaeaea !important;
  background-color: #eaeaea !important;
}
.e-cancel:hover {
  border-color: #e9e9e9 !important;
  background-color: #e9e9e9 !important;
}
</style>
src/pages/settings/components/SystemMaintenance.vue
New file
@@ -0,0 +1,514 @@
<template>
    <el-tabs
      id="systemMaintenance"
      v-model="activeName"
      v-loading="loading"
      :element-loading-text="loadingText"
    >
      <el-tab-pane label="设备维护" name="first" v-if="isShow('videoSystem:sysManage:sysfix')">
        <div class="s-system-maintenance">
        <div class="box-card">
          <div class="ui-top-view">
            <div class="ui-top-title">重启</div>
          </div>
          <el-divider></el-divider>
          <div class="box-card-content">
            <el-row>
              <el-col :span="1">
                <el-button type="primary" size="small" style="width:80px" @click="reboot">重启</el-button>
              </el-col>
              <el-col :span="23">
                <b class="card-text">重启节点</b>
              </el-col>
            </el-row>
            <el-row style="margin-top:20px">
              <el-col>
                <vue-cron :expression="rebootCron" @update="setRebootCron" />
              </el-col>
            </el-row>
          </div>
        </div>
        <!--
        <div class="box-card">
          <div class="ui-top-view">
            <div class="ui-top-title">恢复默认值</div>
          </div>
          <el-divider></el-divider>
          <div class="box-card-content">
            <el-row>
              <el-col :span="1">
                <el-button type="primary" size="small">简单恢复</el-button>
              </el-col>
              <el-col :span="23">
                <b class="card-text">简单恢复设备参数</b>
              </el-col>
            </el-row>
            <el-row style="margin-top:20px">
              <el-col :span="1">
                <el-button type="primary" size="small">完全恢复</el-button>
              </el-col>
              <el-col :span="23">
                <b class="card-text">完全恢复设备参数到出厂设置</b>
              </el-col>
            </el-row>
          </div>
        </div>
        <div class="box-card">
          <div class="ui-top-view">
            <div class="ui-top-title">参数导入导出</div>
          </div>
          <el-divider></el-divider>
          <div class="box-card-content">
            <el-row :gutter="4">
              <el-col :span="1">
                <el-button type="info" size="small" style="width:80px">导入</el-button>
              </el-col>
              <el-col :span="3" style="padding-left:30px">
                <el-input placeholder="上传参数文件" size="small" :readonly="true">
                  <el-upload slot="suffix" action="https://jsonplaceholder.typicode.com/posts/">
                    <el-button
                      type="text"
                      icon="el-icon-upload2"
                      size="small"
                      style="font-size:18px; color:#0088ff"
                    ></el-button>
                  </el-upload>
                </el-input>
              </el-col>
            </el-row>
            <el-row style="margin-top:20px">
              <el-col :span="1">
                <el-button type="primary" size="small">设备参数</el-button>
              </el-col>
              <el-col :span="23">
                <b class="card-text">参数导出</b>
              </el-col>
            </el-row>
          </div>
        </div>
        -->
        <div class="box-card">
          <div class="ui-top-view">
            <div class="ui-top-title">升级</div>
          </div>
        </div>
        <el-divider></el-divider>
        <div class="box-card-content">
          <el-row :gutter="4">
            <el-col :span="6">
              <file-uploader
                single
                uploadPlaceholder="上传升级文件"
                url="/data/api-v/sysset/patchUpdate"
                @complete="onFileUpload"
                @file-added="onFileAdded"
              />
            </el-col>
            <el-col :span="2">
              <el-button
                type="primary"
                size="small"
                style="width:80px"
                @click="upgrade"
                :disabled="!fileAdded"
                :loading="upgrading"
              >升级</el-button>
            </el-col>
            <el-col :span="16" class="upload-msg">
              <span v-html="patchUpdateStatus"></span>
            </el-col>
          </el-row>
        </div>
      </div>
      </el-tab-pane>
      <el-tab-pane label="数据库维护" name="second" v-if="isShow('videoSystem:sysManage:dbfix')">
        <div class="box">
          <p class="title">
            <label>数据清理</label>
          </p>
          <div class="range">
            <div class="left">
              <p>选择数据范围:</p>
            </div>
            <div class="middle">
              <el-date-picker
                v-model="dataRange"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                style="height:38px"
                :picker-options="pickerOptions"
              ></el-date-picker>
            </div>
            <div class="right">
              <el-button @click="deleteData" style="height:38px;background:#ff0000;color:white">删除数据</el-button>
            </div>
          </div>
          <div class="tip">
            <i class="iconfont icontishi-zhuyi"></i>
            <p class="zhuyi">请注意,按以上日期范围删除的数据不可恢复,立即生效,请谨慎操作</p>
          </div>
        </div>
      </el-tab-pane>
    </el-tabs>
</template>
<script>
import { rebootServer, getDevInfo, getRebootTask, setRebootTask, fileUpload, doUpgrade,deleteDate } from "@/api/system"
import VueCron from "@/components/subComponents/VueCron"
import FileUploader from "@/components/subComponents/FileUpload/index"
export default {
  components: {
    VueCron,
    FileUploader
  },
  data() {
    return {
      timer: null,
      buttonAuthority: sessionStorage.getItem("buttonAuthoritys") || [],
      rebootCron: "",
      activeName: "first",
      restartValue: "不重启",
      restartTimeValue: new Date(2019, 9, 10, 18, 40),
      loading: false,
      loadingText: '',
      probeSum: 0,
      patchUpdateStatus: "",
      dataRange: [
        this.$moment().format("YYYY-MM-DD HH:mm:ss"),
        this.$moment().format("YYYY-MM-DD HH:mm:ss")
      ],
      fileUploadUrl: fileUpload,
      patchFile: {},
      pickerOptions: {
        disabledDate(time) {
          var day = new Date()
          day.setTime(day.getTime() - 24 * 60 * 60 * 1000)
          return time.getTime() > day;
        },
      },
      upgrading: false,
      fileAdded: false
    };
  },
  mounted() {
    this.getRebootCron()
    if (!this.isShow('videoSystem:sysManage:sysfix')) {
      console.log("默认显示数据库维护")
      this.activeName = "second"
    }
  },
  computed: {
    isAdmin() {
      if (
        sessionStorage.getItem('userInfo') &&
        sessionStorage.getItem('userInfo') !== ''
      ) {
        let loginName = JSON.parse(sessionStorage.getItem('userInfo')).username
        return (
          loginName === 'superadmin' || loginName === 'basic'
        )
      }
      return false;
    }
  },
  methods: {
    isShow (authority) {
      if (this.isAdmin) {
        return true
      } else if (
        this.buttonAuthority.indexOf(',' + authority + ',') > -1
      ) {
        return true
      } else {
        return false
      }
    },
    format(array) {
      return [
        this.$moment(array[0]).format("YYYY-MM-DD"),
        this.$moment(array[1]).format("YYYY-MM-DD")
      ];
    },
    getRebootCron() {
      getRebootTask().then(rsp => {
        this.rebootCron = rsp.data
      })
    },
    setRebootCron(value) {
      this.rebootCron = value
      setRebootTask({ task: value }).then(rsp => {
        if (rsp && rsp.success) {
          this.$notify({
            type: "success",
            message: "配置成功"
          })
        }
      }).catch(err => {
        this.$notify({
          type: "error",
          message: "配置失败"
        })
      })
    },
    reboot() {
      this.$confirm('确定要重启该节点吗?', {
        center: true,
        cancelButtonClass: 'comfirm-class-cancle',
        confirmButtonClass: 'comfirm-class-sure'
      }).then(() => {
        this.loading = true;
        this.loadingText = "智能计算节点正在重启,请耐心等待..."
        rebootServer().then(rsp => {
          this.probeServer(this.reLogin)
        }).catch(err => {
          if (err.status == 400) {
            this.loading = false;
            this.$notify({
              type: "error",
              message: "重启计算节点失败"
            })
          } else {
            this.probeServer(this.reLogin)
          }
        })
      })
    },
    deleteData() {
      var timeRange = this.format(this.dataRange);
      var showStartTime = timeRange[0]
      var showEndTime = timeRange[1]
      console.log("时间:",showStartTime,showEndTime)
      this.$confirm("提示:"+showStartTime+" 至 "+showEndTime+" 产生的全部数据将被删除,此操作立即生效,不可恢复,是否删除?", {
        center: true,
        cancelButtonClass: "comfirm-class-cancle",
        confirmButtonClass: "comfirm-class-sure"
      }).then(() => {
        this.loading = true
        this.loadingText = "正在删除数据,请稍候!"
        var param = {
          startTime: showStartTime,
          endTime: showEndTime
        }
        deleteDate(param).then(resp => {
          if (resp.success) {
            this.$message({
              type: "success",
              message: "删除数据成功"
            })
            this.loading = false
          }
        }).catch(err => {
          this.$message({
            type: "error",
            message: "删除数据失败!"
          })
          this.loading = false
        })
      }).catch(() => {
        console.log("取消了!")
      })
    },
    reLogin() {
      this.$router.push("/")
    },
    probeServer(callback) {
      this.probeSum++;
      let _this = this
      if (this.probeSum > 60) {
        this.$confirm('连接服务器失败, 请刷新页面或联系管理员', '失败', {
          type: 'error',
          cancelButtonClass: 'comfirm-class-cancle',
          confirmButtonClass: 'comfirm-class-sure'
        }).then(() => {
          // _this.$router.push("/")
          callback()
        })
        return
      }
      this.timer = setTimeout(() => {
        getDevInfo().then(() => {
          // _this.$router.push("/")
          callback()
        }).catch(err => {
          _this.probeServer(callback)
        })
      }, 10000)
    },
    onFileUpload(file) {
      this.patchUpdateStatus = `<span style="color:green">上传成功, 点击升级按钮开始升级</span>`
      this.patchFile = { ...file }
      this.fileAdded = true
    },
    onFileAdded() {
      this.patchUpdateStatus = ""
    },
    upgrade() {
      this.upgrading = true
      this.patchUpdateStatus = `<span style="color:red">正在升级...</span>`
      doUpgrade(this.patchFile).then(rsp => {
        this.upgrading = false
        if (rsp && rsp.success) {
          clearTimeout(this.timer)
          this.doneUpgrade()
        }
      }).catch(err => {
        if (err.code) {
          this.upgrading = false
          this.patchUpdateStatus = `<span style="color:red">${err.data}</span>`
          clearTimeout(this.timer)
        } else {
          this.probeServer(this.doneUpgrade)
        }
      })
    },
    doneUpgrade() {
      this.upgrading = false
      this.patchUpdateStatus = `<span style="color:green">升级成功</span>`
      let _this = this
      this.$confirm('升级成功, 请重新登录系统', '成功', {
        type: 'success',
        cancelButtonClass: 'comfirm-class-cancle',
        confirmButtonClass: 'comfirm-class-sure'
      }).then(() => {
        _this.reLogin()
      })
    }
  }
};
</script>
<style lang="scss">
.s-system-maintenance {
  width: 100%;
  height: 100%;
  .box-card {
    text-align: left;
    height: auto;
    margin: 10px 0px;
    .box-card-content {
      padding-bottom: 40px;
      .card-text {
        padding: 0 30px;
        line-height: 32px;
      }
    }
  }
  .upload-icon {
    font-size: 18px;
    color: #0088ff;
  }
  .upload-msg {
    padding-left: 10px;
    text-align: left;
    span {
      line-height: 32px;
      font-size: 13px;
    }
  }
}
.box{
  width: 50%;
  min-width: 700px;
  height: 270px;
  border: 1px solid #eee;
  .title {
    font-size:20px;
    font-weight: bold;
    text-align: left;
    padding: 20px;
    border-bottom: 1px solid #eee;
  }
  .range {
    width: 100%;
    padding-top: 30px;
    height: 38px;
    .left {
      width: 120px;
      float: left;
      text-align: right;
      font-size: 14px;
      p {
        height: 38px;
        line-height: 38px;
      }
    }
    .middle {
      width: 50%;
      min-width: 400px;
      height: 38px;
      float: left;
    }
    .right {
      width: 20%;
      height: 38px;
      float: left;
    }
  }
  .tip {
    width: 100%;
    padding: 30px 0px 0px 30px;
    height: 34px;
    .zhuyi {
      font-size: 14px;
      height: 34px;
      line-height: 34px;
      margin-left: 20px;
      float: left;
    }
    i {
      font-size: 32px;
      color: #e99038;
      float: left;
    }
  }
}
#systemMaintenance{
  .el-tabs__header {
      border: 0px solid #dcdfe6;
      .el-tabs__item {
        padding: 5px 50px;
        height: 50px;
        font-family: PingFangSC-Regular;
        font-size: 14px;
        color: #222222;
        text-align: center;
        border: 0px solid transparent;
      }
      .el-tabs__item:nth-child(2) {
        padding-left: 50px;
      }
      .el-tabs__item:last-child {
        padding-right: 50px;
      }
      .el-tabs__item.is-active {
        color: #ff7733;
        font-weight: bold;
        // border-right-color: #fff;
        // border-left-color: #fff;
      }
      .el-tabs__item:not(.is-disabled):hover {
        color: #ff7733;
      }
  }
  .el-tabs__active-bar {
    background-color: #ff7733;
  }
  .el-tabs__content {
    padding-left: 15px !important;
  }
}
</style>
src/pages/settings/index/App.vue
@@ -1,1831 +1,161 @@
<template>
  <div class="container" v-if="!showWelcome">
    <div class="container-left">
      <div
        class="left-card"
        :class="activeIndex == index ? 'left-card-active' : ''"
        v-for="(item, index) in menuArr"
        :key="index"
        @click="openMenu(item.name, index)"
      >
        <span class="icon iconfont">&#xe646;</span>
        <span class="card-text">{{ item.name }}</span>
      </div>
    </div>
    <div
      class="container-center"
      v-if="activePage == '账户' || activePage == '日期时间'"
    >
      <div class="account-left" v-if="activePage == '账户'">
        <div class="account-list">
          <div
            class="account-card"
            :class="activeAccountIndex == index ? 'account-card-active' : ''"
            v-for="(item, index) in accountArr"
            :key="index"
            ref="account-card"
            @click="openAccount(item, index)"
          >
            <div class="touxiang">
              <img
                v-if="item.headpic"
                :src="`data:image/png;base64,${item.headpic}`"
                alt=""
              />
            </div>
            <span class="user-name">{{ item.username }}</span>
          </div>
        </div>
        <div class="add-account">
          <span class="icon iconfont" @click="showAddAccount">&#xe646;</span>
        </div>
      </div>
      <div class="datetime-left" v-if="activePage == '日期时间'">
        <div class="time-card">
          <div class="head">
            <span class="icon iconfont">&#xe690;</span>
            <span>设备时间</span>
          </div>
          <div class="time-main">{{ equipmentTime }}</div>
          <div class="date-bot">
            <span class="year">{{ equipmentDate }}</span>
            <span class="week">{{ weekday }}</span>
          </div>
        </div>
        <div class="line">
          <div class="name">NTP校时</div>
          <el-switch
            v-model="isNtp"
            @change="changeSwitch('isNtp')"
            active-color="rgba(61, 104, 225, 1)"
          >
          </el-switch>
        </div>
        <div class="line">
          <div class="name">手动校对</div>
          <el-switch
            v-model="isManual"
            @change="changeSwitch('isManual')"
            active-color="rgba(61, 104, 225, 1)"
          >
          </el-switch>
        </div>
      </div>
    </div>
    <div
      class="container-right"
      v-if="activePage == '账户' || activePage == '日期时间'"
    >
      <div class="account-right" v-if="activePage == '账户'">
        <div
          class="account-content"
          v-if="inAccountDetail == false && isAddAccount == false"
        >
          <div class="content-top">
            <div class="touxiang-big">
              <img
                v-if="activeAccountItem.headpic"
                :src="`data:image/png;base64,${activeAccountItem.headpic}`"
                alt=""
              />
            </div>
            <div class="user-desc">
              <div class="username">
                <span class="icon iconfont" style="margin-right: 5px"
                  >&#xe690;</span
                >
                <span>{{ activeAccountItem.username }}</span>
              </div>
              <div class="nickname">
                <span>昵称:</span>
                <span v-show="!showInputNickName">{{
                  activeAccountItem.nickname
                }}</span>
                <input
                  type="text"
                  class="input-nick"
                  ref="input-nick"
                  v-show="showInputNickName"
                  v-model="inputNickName"
                  @blur="hideInputNick"
                  @keydown.enter="blurInputNick"
                />
                <span class="icon iconfont" @click="editNickName"
                  >&#xe60c;</span
                >
              </div>
              <div class="user-role">
                {{
                  activeAccountItem.sysRoles.length
                    ? activeAccountItem.sysRoles[0].name
                    : ""
                }}
              </div>
            </div>
          </div>
          <div class="list-btn">
            <div class="item-btn" @click="showChangePassword">修改密码</div>
            <div class="item-btn" @click="deleteAccount">删除账户</div>
            <div class="item-btn" @click="openPermission">权限设置</div>
          </div>
        </div>
        <div class="change-pw" v-if="inAccountDetail && isChangePw">
          <div class="title">修改密码</div>
          <el-form
            :model="passwordForm"
            :rules="pwRules"
            ref="passwordForm"
            class="password-form"
          >
            <el-form-item prop="curPassword">
              <div class="p-title">当前密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.curPassword"
                show-password
              ></el-input>
            </el-form-item>
            <el-form-item prop="newPassword">
              <div class="p-title">新密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.newPassword"
                show-password
              ></el-input>
            </el-form-item>
            <el-form-item prop="confirmPassword">
              <div class="p-title">确认密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.confirmPassword"
                show-password
              ></el-input>
            </el-form-item>
          </el-form>
          <div class="btns">
            <div class="cancel" @click="cancelPassword">取消</div>
            <div class="ok" @click="SaveNewPassword('passwordForm')">保存</div>
          </div>
        </div>
        <div class="permission" v-if="inAccountDetail && isSetPermission">
          <div class="title">权限管理</div>
          <div class="line-wrap" v-for="item in sysMenus" :key="item.id">
            <div class="line">
              <div class="name">{{ item.name }}</div>
              <el-switch
                v-model="item.selected"
                active-color="rgba(61, 104, 225, 1)"
                @change="fatherChange(item)"
              >
              </el-switch>
            </div>
            <div v-if="item.children">
              <div
                class="line"
                style="margin-left: 55px"
                v-for="x in item.children"
                :key="x.id"
              >
                <div class="name">{{ x.name }}</div>
                <el-switch
                  v-model="x.selected"
                  active-color="rgba(61, 104, 225, 1)"
                  @change="childrenChange(item)"
                >
                </el-switch>
              </div>
            </div>
          </div>
          <div class="btns">
            <div class="cancel" @click="cancelSet">取消</div>
            <div class="ok" @click="saveAuth">保存</div>
          </div>
        </div>
        <div class="add-account-page" v-if="isAddAccount">
          <div class="title">添加账户</div>
          <div class="upload-group">
            <div
              class="upload-jpg"
              :class="
                selectedPic == index? 'upload-jpg-border' : ''
              "
              v-for="(item, index) in jpgArr"
              :key="index"
              @click="pickHeadDefPic(item, index)"
            >
              <img
                v-if="item"
                :src="`data:image/png;base64,${item.path}`"
                alt=""
                srcset=""
              />
              <div class="img-mask" v-if="selectedPic == index">
                <span class="icon iconfont enable">&#xe62a;</span>
              </div>
            </div>
            <!-- <el-upload
              class="upload-demo"
              action="https://jsonplaceholder.typicode.com/posts/"
              :show-file-list="false"
              :http-request="uploadUserPic"
            >
              <div v-if="loadedPic == ''" class="upload-jpg-up">上传</div>
            </el-upload> -->
          </div>
          <div class="fill-group">
            <el-form
              :model="addForm"
              :rules="rules"
              ref="addForm"
              class="add-form"
            >
              <el-form-item prop="userName">
                <div class="p-title">用户名:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.userName"
                ></el-input>
              </el-form-item>
              <el-form-item prop="roleId">
                <div class="p-title">角色:</div>
                <el-select v-model="addForm.roleId" placeholder="请选择角色">
                  <el-option
                    v-for="(item, i) in roleList"
                    :key="i"
                    :label="item.name"
                    :value="item.id"
                  ></el-option>
                </el-select>
              </el-form-item>
              <el-form-item prop="nickName">
                <div class="p-title">昵称:</div>
                <el-input
                  placeholder="选填"
                  v-model="addForm.nickName"
                ></el-input>
              </el-form-item>
              <el-form-item prop="password">
                <div class="p-title">密码:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.password"
                  show-password
                ></el-input>
              </el-form-item>
              <el-form-item prop="confirmPassword">
                <div class="p-title">确认密码:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.confirmPassword"
                  show-password
                ></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="btns">
            <div class="cancel" @click="cancelAdd">取消</div>
            <div class="ok" @click="saveAddAccount('addForm')">保存</div>
          </div>
        </div>
      </div>
      <div class="datetime-right" v-if="activePage == '日期时间'">
        <div class="ntp-time" v-if="isNtp">
          <el-form label-width="160px">
            <el-form-item label="服务器地址">
              <!-- :disabled="syncType === '2'" -->
              <ip-input
                :ip="ntpServer"
                @on-blur="ntpServer = arguments[0]"
              ></ip-input>
            </el-form-item>
            <el-form-item label="校时时间间隔(分钟)">
              <div class="right">
                <el-input-number
                  v-model.number="timeInterval"
                  :min="1"
                  :max="60"
                  placeholder="请输入"
                  size="small"
                  :controls="false"
                ></el-input-number>
                <el-button
                  type="text"
                  @click="testNTP"
                  :loading="ntpTestLoading"
                  >测试</el-button
                >
              </div>
            </el-form-item>
          </el-form>
        </div>
        <div class="manual-time" v-if="isManual">
          <switchBar
            :barName="`同步本计算机时间`"
            @switchChange="syncBrowser"
            :value="isSyncBrowser"
          ></switchBar>
          <div class="clock-wrap">
            <div class="clock">
              <div class="hour">
                <div class="dnum" @click="showInput('Hour')">
                  <span v-show="!showHourInput">{{ syncHour }}</span>
                  <input
                    class="input-box"
                    v-show="showHourInput"
                    ref="iptHour"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>23)value='23'"
                    type="text"
                    v-model="inputHour"
                    @blur="hideInput('Hour')"
                    @keydown.enter="hideInput('Hour')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('hrs')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('hrs')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
              <div class="sep">:</div>
              <div class="mins">
                <div class="dnum" @click="showInput('Min')">
                  <span v-show="!showMinInput">{{ syncMin }}</span>
                  <input
                    class="input-box"
                    v-show="showMinInput"
                    ref="iptMin"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>59)value='59'"
                    type="text"
                    v-model="inputMin"
                    @blur="hideInput('Min')"
                    @keydown.enter="hideInput('Min')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('min')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('min')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
              <div class="sep">:</div>
              <div class="mins">
                <div class="dnum" @click="showInput('Sec')">
                  <span v-show="!showSecInput">{{ syncSec }}</span>
                  <input
                    class="input-box"
                    v-show="showSecInput"
                    ref="iptSec"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>59)value='59'"
                    type="text"
                    v-model="inputSec"
                    @blur="hideInput('Sec')"
                    @keydown.enter="hideInput('Sec')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('sec')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('sec')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
            </div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('yrs')">-</div>
            <div class="middle" @click="showInput('Yrs')">
              <span v-show="!showYrsInput">{{ syncYrs }}</span>
              <input
                class="input-box"
                v-show="showYrsInput"
                ref="iptYrs"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>4)value=value.slice(0,4);"
                type="text"
                v-model="inputYrs"
                @blur="hideInput('Yrs')"
                @keydown.enter="hideInput('Yrs')"
              />
              年
            </div>
            <div class="plus" @click="plusOne('yrs')">+</div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('mth')">-</div>
            <div class="middle" @click="showInput('Month')">
              <span v-show="!showMonthInput">{{ syncMonth }}</span>
              <input
                class="input-box"
                v-show="showMonthInput"
                ref="iptMonth"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>12)value='12'"
                type="text"
                v-model="inputMonth"
                @blur="hideInput('Month')"
                @keydown.enter="hideInput('Month')"
              />
              月
            </div>
            <div class="plus" @click="plusOne('mth')">+</div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('day')">-</div>
            <div class="middle" @click="showInput('Day')">
              <span v-show="!showDayInput">{{ syncDay }}</span>
              <input
                class="input-box"
                v-show="showDayInput"
                ref="iptDay"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>31)value='31'"
                type="text"
                v-model="inputDay"
                @blur="hideInput('Day')"
                @keydown.enter="hideInput('Day')"
              />
              日
            </div>
            <div class="plus" @click="plusOne('day')">+</div>
          </div>
        </div>
        <div class="btns">
          <div class="cancel" @click="cancelPassword">取消</div>
          <div class="ok" @click="submitClock">保存</div>
        </div>
      </div>
    </div>
    <clusterManagement
      v-if="activePage == '集群管理'"
      style="width: 100%"
    ></clusterManagement>
    <netSettings
      v-if="activePage == '网络设置'"
      style="width: 100%"
    ></netSettings>
    <keyboardLanguage
      v-if="activePage == '键盘和语言'"
      style="width: 100%"
    ></keyboardLanguage>
    <generalSettings
      v-if="activePage == '通用设置'"
      style="width: 100%"
    ></generalSettings>
  </div>
  <div class="welcome-page" v-else>
    <div class="child" @click="openWelcome('账户',0)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6de;</span>
        <span class="welcome-title">账户</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('日期时间',1)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6ff;</span>
        <span class="welcome-title">日期时间</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('集群管理',2)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6df;</span>
        <span class="welcome-title">集群管理</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('网络设置',3)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6dd;</span>
        <span class="welcome-title">网络设置</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('键盘和语言',4)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6dc;</span>
        <span class="welcome-title">键盘和语言</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('通用设置',5)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6db;</span>
        <span class="welcome-title">通用设置</span>
      </div>
    </div>
  <div class="s-system-manage">
    <basic-setting v-show="activeName === 'basic'"></basic-setting>
  </div>
</template>
<script>
import { getClockInfo, saveClockInfo, testNTPserver } from "@/api/system";
import {
  uploadHeadPic,
  addUser,
  getUsers,
  updateUser,
  updataUser,
  updatePassword,
  deleteUser,
  getUserMenus,
  defHeadPics,
  getRoles,
} from "@/api/user";
import switchBar from "../components/switchBar";
import ipInput from "@/components/subComponents/IPInput";
import clusterManagement from "../views/clusterManagement";
import netSettings from "../views/NetSettings";
import keyboardLanguage from "../views/keyboardLanguage";
import generalSettings from "../views/generalSettings";
import BasicSetting from "../components/BasicSetting";
export default {
  name: "settings",
  name: 'settings',
  components: {
    switchBar,
    ipInput,
    clusterManagement,
    netSettings,
    keyboardLanguage,
    generalSettings,
    BasicSetting
  },
  data() {
    var validatePass2 = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.addForm.password) {
        callback(new Error("两次输入密码不一致!"));
      } else {
        callback();
      }
    };
    var validatePass4 = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.passwordForm.newPassword) {
        callback(new Error("两次输入密码不一致!"));
      } else {
        callback();
      }
    };
    return {
      showAccount: true,
      syncYrs: "",
      activeAccountItem: {},
      syncMonth: "",
      syncHour: "",
      showWelcome: true,
      syncDay: "",
      syncMin: "",
      syncSec: "00",
      isSyncBrowser: false,
      showDateTime: false,
      isAddAccount: false,
      isCount: false,
      isCalculate: false,
      browserTimer: null,
      timezone: "",
      isRealtime: false,
      inputNickName: "",
      showHourInput: false,
      showMinInput: false,
      showSecInput: false,
      showYrsInput: false,
      showMonthInput: false,
      selectedPic: null,
      showDayInput: false,
      loadedPic: "",
      cameraInfo: false,
      dependentScene: false,
      timestamp: 0,
      inAccountDetail: false,
      isChangePw: false,
      isSetPermission: false,
      timeInterval: 10,
      ntpServer: "",
      syncType: "1",
      equipmentTime: "",
      equipmentDate: "",
      roleList: [],
      ntpTestLoading: false,
      settime: "",
      weekday: "",
      menuArr: [
        { name: "账户" },
        { name: "日期时间" },
        { name: "集群管理" },
        { name: "网络设置" },
        { name: "键盘和语言" },
        { name: "通用设置" },
      ],
      accountArr: [],
      jpgArr: [],
      isManual: false,
      isNtp: true,
      activePage: "账户",
      activeIndex: 0,
      clockTimer: null,
      inputHour: "",
      inputMin: "",
      inputSec: "",
      inputYrs: "",
      showInputNickName: false,
      inputMonth: "",
      inputDay: "",
      passwordForm: {
        curPassword: "",
        newPassword: "",
        confirmPassword: "",
      },
      activeAccountIndex: 0,
      sysMenus: [],
      addForm: {
        userName: "",
        nickName: "",
        password: "",
        headpic: "",
        confirmPassword: "",
        roleId: "",
      },
      rules: {
        userName: [
          { required: true, message: "请输入用户名", trigger: "blur" },
          {
            min: 3,
            max: 10,
            message: "长度在 3 到 10 个字符",
            trigger: "blur",
          },
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        confirmPassword: [{ validator: validatePass2, trigger: "blur" }],
      },
      pwRules: {
        // pw: [{ validator: validatePass3, trigger: "blur" }],
        curPassword: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        newPassword: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        confirmPassword: [{ validator: validatePass4, trigger: "blur" }],
      },
    };
  },
  beforeDestroy() {
    clearTimeout(this.clockTimer);
    clearInterval(this.browserTimer);
  },
  mounted() {
    this.fetchUserList();
    this.fetchDefHeadPic();
  },
  methods: {
    fetchDefHeadPic() {
      defHeadPics().then((res) => {
        this.jpgArr = res.data;
      });
    },
    fetchUserList(showLast = false) {
      getUsers().then((res) => {
        this.accountArr = res.data;
        if (this.accountArr.length) {
          this.activeAccountItem = this.accountArr[0];
          this.activeAccountIndex = 0;
        }
        if (showLast) {
          this.cancelAdd();
          const lastIdx = this.accountArr.length - 1;
          this.openAccount(this.accountArr[lastIdx], lastIdx);
        }
      });
    },
    blurInputNick() {
      this.$refs["input-nick"].blur();
    },
    editNickName() {
      this.showInputNickName = true;
      this.inputNickName = this.activeAccountItem.nickname;
      this.$refs["input-nick"].focus();
    },
    hideInputNick() {
      this.showInputNickName = false;
      updateUser({
        id: this.activeAccountItem.id,
        nickname: this.inputNickName,
      }).then((res) => {
        this.$message.success(res.msg);
        this.fetchUserList(true);
      });
    },
    openAccount(item, i) {
      this.activeAccountItem = item;
      this.activeAccountIndex = i;
      this.inAccountDetail = false;
      this.cancelSet();
      this.fetchMenu();
    },
    minusOne(typ) {
      this.isSyncBrowser = false;
      this.syncBrowser(false);
      let num;
      switch (typ) {
        case "hrs":
          num = +this.syncHour - 1;
          if (num == -1) {
            num = 23;
          }
          this.syncHour = this.padZero(num);
          break;
        case "min":
          num = +this.syncMin - 1;
          if (num == -1) {
            num = 59;
          }
          this.syncMin = this.padZero(num);
          break;
        case "sec":
          num = +this.syncSec + 1;
          if (num == -1) {
            num = 59;
          }
          this.syncSec = this.padZero(num);
          break;
        case "yrs":
          num = +this.syncYrs - 1;
          this.syncYrs = this.padZero(num);
          break;
        case "mth":
          num = +this.syncMonth - 1;
          if (num == 0) {
            num = 12;
            this.minusOne("yrs");
          }
          this.syncMonth = this.padZero(num);
          break;
        case "day":
          num = +this.syncDay - 1;
          if (num == 0) {
            this.minusOne("mth");
            const maxDay = new Date(
              +this.syncYrs,
              +this.syncMonth,
              0
            ).getDate();
            num = maxDay;
          }
          this.syncDay = this.padZero(num);
          break;
        default:
          break;
      }
    },
    fatherChange(item) {
      item.children.forEach((x) => {
        x.selected = item.selected;
      });
    },
    childrenChange(item) {
      let isAllSelected = item.children.every((x) => x.selected == true);
      item.selected = isAllSelected;
    },
    plusOne(typ) {
      this.isSyncBrowser = false;
      this.syncBrowser(false);
      let num;
      switch (typ) {
        case "hrs":
          num = +this.syncHour + 1;
          if (num == 24) {
            num = 0;
          }
          this.syncHour = this.padZero(num);
          break;
        case "min":
          num = +this.syncMin + 1;
          if (num == 60) {
            num = 0;
          }
          this.syncMin = this.padZero(num);
          break;
        case "sec":
          num = +this.syncSec + 1;
          if (num == 60) {
            num = 0;
          }
          this.syncSec = this.padZero(num);
          break;
        case "yrs":
          num = +this.syncYrs + 1;
          this.syncYrs = this.padZero(num);
          break;
        case "mth":
          num = +this.syncMonth + 1;
          if (num == 13) {
            num = 1;
          }
          this.syncMonth = this.padZero(num);
          break;
        case "day":
          num = +this.syncDay + 1;
          const maxDay = new Date(+this.syncYrs, +this.syncMonth, 0).getDate();
          if (num > maxDay) {
            num = 1;
          }
          this.syncDay = this.padZero(num);
          break;
        default:
          break;
      }
    },
    submitClock() {
      if (this.syncType === "1") {
        if (this.ntpServer === "" || this.ntpServer === "...") {
          this.$notify({
            type: "error",
            message: "NTP 服务器地址不能为空",
          });
          return false;
        } else if (this.timeInterval === "") {
          this.timeInterval = 1;
        }
      } else if (this.isSyncBrowser) {
        if (this.settime === "") {
          this.$notify({
            type: "error",
            message: "设置时间不能为空",
          });
          return false;
        }
      } else {
        this.settime = `${this.syncYrs}-${this.syncMonth}-${this.syncDay} ${this.syncHour}:${this.syncMin}:${this.syncSec}`;
      }
      saveClockInfo({
        timeZone: this.timezone,
        ntp: this.syncType === "1",
        ntpServer: this.ntpServer,
        interval: this.timeInterval,
        newTime: this.settime,
      }).then((rsp) => {
        if (rsp && rsp.success) {
          this.$notify({
            type: "success",
            message: "设置成功",
          });
        }
        this.initClockConf();
      });
    },
    flatGetArr(arr, res) {
      for (const item of arr) {
        if (item.selected) res.push(item.id);
        if (item.children) this.flatGetArr(item.children, res);
      }
    },
    saveAuth() {
      let arr = [];
      this.flatGetArr(this.sysMenus, arr);
      updataUser({
        id: this.activeAccountItem.id,
        menuIds: arr,
      }).then((res) => {
        if (res.success) {
          this.$message.success(res.msg);
          this.cancelSet();
        }
      });
    },
    formatTime(number, format) {
      var formateArr = ["Y", "M", "D", "h", "m", "s"];
      var returnArr = [];
      var date = new Date(number * 1000);
      returnArr.push(date.getFullYear());
      returnArr.push(this.formatNumber(date.getMonth() + 1));
      returnArr.push(this.formatNumber(date.getDate()));
      returnArr.push(this.formatNumber(date.getHours()));
      returnArr.push(this.formatNumber(date.getMinutes()));
      returnArr.push(this.formatNumber(date.getSeconds()));
      this.weekday = "星期" + "日一二三四五六".charAt(date.getDay());
      for (var i in returnArr) {
        format = format.replace(formateArr[i], returnArr[i]);
      }
      return format;
    },
    padZero(n) {
      n = +n;
      return n < 10 ? "0" + n : "" + n;
    },
    formatNumber(n) {
      n = n.toString();
      return n[1] ? n : "0" + n;
    },
    // uploadUserPic(params) {
    //   let param = new FormData();
    //   param.append("file", params.file);
    //   uploadHeadPic(param).then((res) => {
    //     this.jpgArr.push(res.data);
    //     this.loadedPic = res.data;
    //   });
    // },
    initClockConf(ntpTest = false) {
      getClockInfo().then((rsp) => {
        if (rsp && rsp.success) {
          this.timezone = rsp.data.time_zone;
          if (!ntpTest) {
            this.syncType = rsp.data.ntp ? "1" : "2";
            this.isNtp = rsp.data.ntp;
            this.isManual = !rsp.data.ntp;
          }
          if (rsp.data.ntp) {
            this.ntpServer = rsp.data.ntp_server;
            this.timeInterval = rsp.data.interval;
          }
          this.timestamp = rsp.data.local_time;
          if (this.clockTimer === null) {
            this.runClock();
            if (this.isManual) this.parseTime();
          }
        }
      });
    },
    openMenu(name, i) {
      this.activePage = name;
      this.activeIndex = i;
      if (this.activePage == "日期时间") {
        this.initClockConf();
      }
    },
    openWelcome(name,i){
      this.showWelcome = false
      this.openMenu(name,i)
    },
    showInput(typ) {
      this[`show${typ}Input`] = true;
      this.$nextTick(() => {
        this.$refs[`ipt${typ}`].focus();
      });
    },
    hideInput(typ) {
      if (this[`input${typ}`]) {
        this[`sync${typ}`] = this.padZero(this[`input${typ}`]);
      }
      this[`show${typ}Input`] = false;
      this[`input${typ}`] = "";
    },
    syncBrowser(enable) {
      this.isSyncBrowser = enable;
      if (!enable) {
        clearInterval(this.browserTimer);
      } else {
        this.browserTimer = setInterval(() => {
          let timestamp = new Date().getTime() / 1000;
          this.settime = this.formatTime(timestamp, "Y-M-D h:m:s");
          let [arr1, arr2] = this.settime.split(" ");
          [this.syncYrs, this.syncMonth, this.syncDay] = arr1.split("-");
          [this.syncHour, this.syncMin, this.syncSec] = arr2.split(":");
        }, 1000);
      }
    },
    showAddAccount() {
      this.inAccountDetail = false;
      this.isAddAccount = true;
      getRoles().then((res) => {
        if (res.success) {
          this.roleList = res.data;
        }
      });
    },
    cancelAdd() {
      this.inAccountDetail = false;
      this.isAddAccount = false;
      this.$refs["addForm"].resetFields();
      this.selectedPic = null;
    },
    cancelPassword() {
      this.isChangePw = false;
      this.inAccountDetail = false;
      this.$refs["passwordForm"].resetFields();
    },
    SaveNewPassword(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          updatePassword({
            oldPwd: this.passwordForm.curPassword,
            newPwd: this.passwordForm.newPassword,
          }).then(
            (res) => {
              if (res.success) {
                this.$message.success(res.msg);
                this.cancelPassword();
              }
            },
            (err) => {
              this.$message.warning("保存失败," + err.msg);
            }
          );
        }
      });
    },
    saveAddAccount(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          let data = {
            username: this.addForm.userName,
            password: this.addForm.password,
            nickname: this.addForm.nickName,
            headpic: this.addForm.headpic,
          };
          addUser(data).then(
            (res) => {
              this.$message.success(res.data);
              this.fetchUserList(true);
            },
            (err) => {
              this.$message.warning("保存失败," + err.msg);
            }
          );
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    pickHeadDefPic(item, i) {
      this.addForm.headpic = item.path;
      this.selectedPic = i;
    },
    testNTP() {
      this.ntpTestLoading = true;
      testNTPserver({ server: this.ntpServer })
        .then((rsp) => {
          if (rsp && rsp.success) {
            this.$notify({
              type: "success",
              message: "时间同步成功",
            });
          } else {
            this.$notify({
              type: "error",
              message: "时间同步失败",
            });
          }
          this.ntpTestLoading = false;
          this.initClockConf(true);
        })
        .catch((err) => {
          this.$notify({
            type: "error",
            message: "时间同步失败,请检查服务器ip",
          });
          this.ntpTestLoading = false;
        });
    },
    cancelSet() {
      this.isSetPermission = false;
      this.inAccountDetail = false;
      this.sysMenus = [];
    },
    showChangePassword() {
      this.isChangePw = true;
      this.inAccountDetail = true;
    },
    runClock() {
      const str = this.formatTime(++this.timestamp, "Y-M-D h:m:s");
      [this.equipmentDate, this.equipmentTime] = str.split(" ");
      this.clockTimer = setTimeout(() => {
        this.runClock();
      }, 1000);
    },
    deleteAccount() {
      this.$confirm("您是否确认删除账户?", "删除账户", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
      })
        .then(() => {
          let obj = {
            ids: [this.activeAccountItem.id],
          };
          deleteUser(obj).then((res) => {
            if (res.success) {
              this.fetchUserList();
              this.$message({
                type: "success",
                message: "删除成功!",
              });
            } else {
              this.$message.warning("删除失败");
            }
          });
        })
        .catch(() => {});
    },
    fetchMenu() {
      getUserMenus({
        userId: this.activeAccountItem.id,
      }).then((res) => {
        if (res && res.success) {
          this.sysMenus = res.data;
        }
      });
    },
    openPermission() {
      this.inAccountDetail = true;
      this.isSetPermission = true;
      if (this.sysMenus.length == 0) {
        this.fetchMenu();
      }
    },
    parseTime() {
      [this.syncYrs, this.syncMonth, this.syncDay] = this.equipmentDate.split(
        "-"
      );
      [this.syncHour, this.syncMin, this.syncSec] = this.equipmentTime.split(
        ":"
      );
    },
    changeSwitch(str) {
      if (str == "isNtp") {
        this.isManual = !this[str];
      } else {
        this.isNtp = !this[str];
      }
      this.syncType = this.isNtp ? "1" : "2";
      if (this.isManual) this.parseTime();
    },
      activeName: "basic",
      buttonAuthority: sessionStorage.getItem("buttonAuthoritys") || [],
      loginName: JSON.parse(sessionStorage.getItem("userInfo")).username || "用户名"
    }
  },
  computed: {
    isShowAddAccount() {
      const info = JSON.parse(sessionStorage.getItem("userInfo"));
      return true;
    isAdmin() {
      if (
        sessionStorage.getItem("userInfo") &&
        sessionStorage.getItem("userInfo") !== ""
      ) {
        let loginName = JSON.parse(sessionStorage.getItem("userInfo")).username;
        return loginName === "superadmin" || loginName === "basic";
      }
      return false;
    }
  },
  methods: {
    isShow(authority) {
      if (this.isAdmin) {
        return true;
      } else if (this.buttonAuthority.indexOf("," + authority + ",") > -1) {
        return true;
      } else {
        return false;
      }
    },
  },
  created() {
    if (this.isShow('videoSystem:base')) {
      this.activeName = "basic"
    } else if (this.isShow('videoSystem:permission')) {
      this.activeName = "user"
    } else if (this.isShow('videoSystem:broadcast')) {
      this.activeName = "radio"
    } else if (this.isShow('videoSystem:eventPush')) {
      this.activeName = "event"
    } else if (this.isShow('videoSystem:logManage')) {
      this.activeName = "log"
    } else if (this.isShow('videoSystem:sysManage')) {
      this.activeName = "system"
    }
  },
};
</script>
<style lang="scss">
.welcome-page {
  width: 100%;
.s-system-manage {
  width: 100% !important;
  min-width: 1067px;
  height: 100%;
  background-color: #f5f5f5;
  display: -ms-flexbox;
  padding: 0 50px;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  .child {
  padding: 10px;
  background-color: #f8f9fb;
  .s-system-manage-breadcrumb {
    height: 5%;
    box-sizing: border-box;
    background-color: white;
    -webkit-box-flex: 0;
    -ms-flex: 0 0 33.3%;
    /* flex: 0 0 16%; */
    float: left;
    width: 250px;
    height: 200px;
    margin: 50px 57px 30px 57px;
    border-radius: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    .child-info {
      display: flex;
      flex-direction: column;
      height: 62%;
      justify-content: space-around;
      .welcome-icon {
        font-size: 72px;
    border: 1px solid #e4e7ed;
    box-shadow: #e4e7ed 0px 0px 9px inset;
    box-shadow: #e4e7ed 0px 0px 9px inset;
    border-radius: 5px;
  }
  .el-tabs--border-card {
    border: 0px solid #dcdfe6;
    -webkit-box-shadow: none;
    box-shadow: none;
    .el-tabs__header {
      border: 0px solid #dcdfe6;
      .el-tabs__item {
        padding: 5px 50px;
        height: 50px;
        font-family: PingFangSC-Regular;
        font-size: 15px;
        color: #222222;
        text-align: center;
        border: 0px solid transparent;
      }
      .welcome-title {
        font-size: 18px;
      .el-tabs__item:nth-child(2) {
        padding-left: 50px !important;
      }
      .el-tabs__item:last-child {
        padding-right: 50px !important;
      }
      .el-tabs__item.is-active {
        color: #3d68e1;
        // border-right-color: #fff;
        // border-left-color: #fff;
      }
      .el-tabs__item:not(.is-disabled):hover {
        color: #3d68e1;
      }
    }
  }
  .child:hover {
    -moz-box-shadow: 5px 5px 10px #ddd;
    -webkit-box-shadow: 5px 5px 10px #ddd;
    box-shadow: 5px 5px 10px #ddd;
    transform: translate3d(0,-2.5px,0);
     transition: all 0.3s;
}
}
.container {
  height: 100%;
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-basis: auto;
  box-sizing: border-box;
  .container-left {
    height: 100%;
     width: 230px;
    overflow: auto;
  .el-tabs__header {
    margin-bottom: 0;
  }
  .el-tabs__content {
    height: calc(100% - 64px);
    box-sizing: border-box;
    flex-shrink: 0;
    padding: 10px;
    border-right: 5px solid rgba(248, 248, 248, 1);
    box-sizing: border-box;
    .left-card {
      height: 55px;
      cursor: pointer;
      border-radius: 12px;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      .iconfont {
        margin-left: 25px;
        margin-right: 10px;
        font-size: 24px;
      }
      .card-text {
    overflow-y: auto;
    padding: 20px 40px !important;
    background: #fff;
    .el-tab-pane {
      width: 100%;
      .s-title {
        text-align: left;
        padding: 15px 0px;
        font-size: 16px;
      }
    }
    .left-card-active {
      background-color: rgba(61, 104, 225, 1);
      color: #fff;
    }
    .left-card:hover {
      background-color: rgba(61, 104, 225, 1);
      color: #fff;
    }
  }
  .container-center {
    height: 100%;
    width: 280px;
    overflow: auto;
    flex-shrink: 0;
    padding: 10px;
    border-right: 5px solid rgba(248, 248, 248, 1);
    box-sizing: border-box;
    .account-left {
      .add-account {
        color: rgba(61, 104, 225, 1);
        margin-top: 30px;
        .iconfont {
          cursor: pointer;
          font-size: 32px;
        }
      }
      .account-card {
        height: 50px;
        background-color: rgba(248, 248, 248, 1);
        margin-bottom: 10px;
        display: flex;
        align-items: center;
        padding: 0 20px;
        box-sizing: border-box;
        border-radius: 10px;
        cursor: pointer;
        .touxiang {
          height: 35px;
          width: 35px;
          background-color: bisque;
          border-radius: 17.5px;
          img {
            border: none;
            height: 35px;
            width: 35px;
            border-radius: 17.5px;
          }
        }
        .user-name {
          margin-left: 10px;
          font-size: 14px;
        }
      }
      .account-card-active {
        background-color: rgba(61, 104, 225, 1);
        color: #fff;
      }
    }
    .datetime-left {
      .time-card {
        height: 105px;
        background-color: rgba(248, 248, 248, 1);
        margin-bottom: 30px;
        border-radius: 10px;
        .head {
          height: 30px;
          line-height: 30px;
          text-align: left;
          box-sizing: border-box;
          padding: 0 10px;
          font-size: 14px;
          .icon {
            margin-right: 5px;
            color: rgba(61, 104, 225, 1);
          }
        }
        .time-main {
          height: 42px;
          line-height: 42px;
          font-family: Consolas;
          font-size: 36px;
        }
        .date-bot {
          height: 25px;
          font-size: 14px;
          line-height: 25px;
          color: #868686;
          display: flex;
          justify-content: space-evenly;
        }
      }
      .line {
        display: flex;
        align-items: center;
        height: 50px;
        padding: 0 25px;
        background-color: rgba(248, 248, 248, 1);
        justify-content: space-between;
        border-radius: 12px;
        margin-bottom: 10px;
        .name {
          font-size: 14px;
        }
      }
    }
  .s-table {
    border: 1px solid #e8e8e9;
    margin-top: 40px;
  }
  .container-right {
    flex: 1;
    flex-basis: auto;
    overflow: auto;
    box-sizing: border-box;
  .ui-top-title {
    padding-bottom: 10px;
    /* border-bottom: 1px solid #ebebeb; */
    position: relative;
    text-align: left;
    padding-left: 15px;
    font-size: 16px;
    font-weight: bold;
  }
    padding: 20px 40px;
    .account-right {
      .account-content {
        .content-top {
          height: 120px;
          width: 350px;
          margin: 0 auto;
          display: flex;
          align-items: center;
          justify-content: center;
          margin-bottom: 20px;
          .touxiang-big {
            width: 100px;
            height: 100px;
            background-color: bisque;
            border-radius: 50px;
            img {
              border: none;
  .ui-top-title:before {
    content: " ";
    border-left: 4px solid #f53d3d;
    display: inline-block;
    height: 16px;
    position: absolute;
    top: 50%;
    left: 0;
    margin-top: -13px;
  }
              width: 100px;
              height: 100px;
              border-radius: 50px;
            }
          }
          .user-desc {
            height: 100px;
            display: flex;
            flex-direction: column;
            align-items: baseline;
            min-width: 200px;
            .username {
              margin: 5px 15px;
              height: 30px;
              line-height: 30px;
              // width: 90px;
              text-align: left;
              font-size: 15px;
              display: flex;
              align-items: center;
            }
            .nickname {
              margin: 5px 15px;
              font-size: 14px;
              .input-nick {
                width: 50px;
                margin-right: 5px;
              }
              .iconfont {
                font-size: 14px;
                margin-left: 5px;
                cursor: pointer;
              }
            }
            .user-role {
              margin: 5px 0 0 15px;
              font-size: 14px;
              color: darkseagreen;
            }
          }
        }
        .list-btn {
          display: flex;
          flex-direction: column;
          align-items: center;
          .item-btn {
            width: 500px;
            height: 45px;
            background-color: #f0f0f0;
            margin-bottom: 15px;
            border-radius: 10px;
            line-height: 45px;
            font-size: 15px;
            cursor: pointer;
          }
          .item-btn:hover {
            color: rgba(255, 153, 102, 1);
          }
        }
      }
      .title {
        height: 30px;
        line-height: 30px;
        /* background-color: aliceblue; */
        margin-bottom: 10px;
        font-size: 16px;
        font-weight: 600;
      }
      .change-pw {
        .p-title {
          text-align: left;
          font-size: 15px;
          margin-top: 5px;
        }
      }
      .el-form-item {
        margin-bottom: 0;
        .el-input__inner {
          background-color: rgba(240, 240, 240, 1);
          border: none;
          border-radius: 12px;
          height: 45px;
          padding: 0 20px;
          font-size: 15px;
        }
        .el-input__clear {
          color: dimgray;
          font-size: 17px;
          line-height: 45px;
        }
        .el-input__suffix {
          right: 1px;
          top: -0.5px;
          width: 45px;
          // background-color: rgba(61, 104, 225, 1);
          /* color: white; */
          border-radius: 12px;
        }
      }
      .permission {
        .line {
          display: flex;
          align-items: center;
          height: 50px;
          padding: 0 25px;
          background-color: rgba(248, 248, 248, 1);
          justify-content: space-between;
          border-radius: 12px;
          margin-bottom: 10px;
          .name {
            font-size: 14px;
          }
        }
      }
      .add-account-page {
        .upload-group {
          height: 120px;
          width: 360px;
          margin: 0 auto;
          overflow: hidden;
          .upload-jpg {
            position: relative;
            height: 48px;
            width: 48px;
            float: left;
            margin: 0 10px;
            margin-bottom: 10px;
            background-color: rgba(242, 242, 242, 1);
            border: 2px solid transparent;
            border-radius: 50%;
            cursor: pointer;
            img {
              height: 48px;
              width: 48px;
              border-radius: 50%;
            }
            .img-mask {
              position: absolute;
              left: 0;
              top: 0;
              height: 48px;
              width: 48px;
              background-color: rgba(0, 0, 0, 0.3);
              color: white;
              border-radius: 50%;
              display: flex;
              justify-content: center;
              align-items: center;
              .selected {
                font-size: 22px;
              }
            }
          }
          .upload-jpg-border {
            border: 2px solid cornflowerblue;
          }
          .upload-jpg-up {
            height: 48px;
            width: 48px;
            float: left;
            display: flex;
            background-color: rgba(242, 242, 242, 1);
            border: 2px solid transparent;
            border-radius: 50%;
            justify-content: center;
            align-items: center;
            font-size: 12px;
            cursor: pointer;
          }
        }
        .fill-group {
          .p-title {
            height: 34px;
            text-align: left;
          }
          .el-form-item {
            .el-select {
              width: 100%;
            }
          }
        }
      }
    }
    .datetime-right {
      .el-form-item.is-required:not(.is-no-asterisk)
        > .el-form-item__label:before,
      .el-form-item.is-required:not(.is-no-asterisk)
        .el-form-item__label-wrap
        > .el-form-item__label:before {
        display: none;
      }
      .el-form-item {
        margin-bottom: 10px;
        height: 50px;
        background: #f8f8f8;
        padding: 4px 20px;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        border-radius: 10px;
        .el-form-item__label {
          text-align: left;
          line-height: 42px;
        }
      }
      .el-form-item__content {
        line-height: 40px;
        position: relative;
        font-size: 14px;
        margin-bottom: 10px;
      }
      .ip-input-container {
        max-width: none !important;
      }
      .ntp-time {
        .right {
          display: flex;
          align-items: baseline;
          .el-input-number--small {
            width: 100%;
          }
          .el-button--text {
            margin-left: 10px;
            text-decoration: underline;
          }
        }
        .ntp-bar {
          height: 40px;
          background-color: rgba(248, 248, 248, 1);
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: 0 10px;
          border-radius: 10px;
          margin-bottom: 10px;
          .title {
            min-width: 70px;
          }
          .input-area {
            width: 450px;
            height: 30px;
            background-color: rgba(240, 240, 240, 1);
            border-radius: 10px;
            line-height: 30px;
            font-size: 14px;
          }
        }
        .int-bar {
          height: 40px;
          background-color: rgba(248, 248, 248, 1);
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: 0 10px;
          border-radius: 10px;
          margin-bottom: 10px;
          .title {
            min-width: 130px;
          }
          .right {
            width: 450px;
            display: flex;
            align-items: center;
            height: 30px;
            .input-area {
              // width: 410px;
              background-color: rgba(240, 240, 240, 1);
              border-radius: 10px;
              line-height: 30px;
              width: -webkit-fill-available;
              font-size: 14px;
            }
            .test {
              width: 40px;
            }
          }
        }
      }
      .manual-time {
        .clock-wrap {
          height: 75px;
          background-color: #f8f8f8;
          display: flex;
          justify-content: center;
          align-items: center;
          margin-bottom: 10px;
          border-radius: 10px;
          .clock {
            display: flex;
            align-items: center;
            height: 90px;
            justify-content: space-evenly;
            .iconfont {
              cursor: pointer;
              color: rgba(134, 134, 134, 1);
            }
            .iconfont:hover {
              background-color: gainsboro;
            }
            .hour {
              background-color: rgba(240, 240, 240, 1);
              display: flex;
              align-items: center;
              width: 100px;
              height: 50px;
              justify-content: space-evenly;
              border-radius: 10px;
            }
            .dnum {
              width: 40px;
              height: 40px;
              line-height: 40px;
              font-size: 34px;
              font-family: Consolas;
              display: flex;
              align-items: center;
              .input-box {
                width: inherit;
                border: none;
                border-radius: 5px;
                height: 35px;
                font-size: 28px;
                text-align: center;
              }
              .input-box:focus {
                outline: none;
              }
            }
            .control {
              width: 20px;
              .fanzhuan {
                display: inline-block;
                -moz-transform: scaleY(-1);
                -webkit-transform: scaleY(-1);
                -o-transform: scaleY(-1);
                transform: scaleY(-1);
              }
            }
            .sep {
              font-family: Consolas;
              width: 40px;
              font-size: 34px;
              height: 40px;
              line-height: 40px;
            }
            .mins {
              background-color: #f0f0f0;
              display: flex;
              align-items: center;
              width: 110px;
              height: 50px;
              justify-content: space-evenly;
              border-radius: 10px;
            }
          }
        }
        .adjust-bar {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 10px;
          border-radius: 10px;
          height: 50px;
          background-color: rgba(248, 248, 248, 1);
          .middle {
            font-size: 14px;
          }
          .input-box {
            width: 80px;
            border: none;
            border-radius: 5px;
            height: 25px;
            font-size: 18px;
            text-align: center;
          }
          .input-box:focus {
            outline: none;
          }
          .minus {
            width: 50px;
            height: 50px;
            background-color: #f0f0f0;
            font-size: 35px;
            border-radius: 10px;
            cursor: pointer;
            line-height: 50px;
            color: rgba(134, 134, 134, 1);
          }
          .plus {
            width: 50px;
            height: 50px;
            cursor: pointer;
            background-color: #f0f0f0;
            font-size: 35px;
            border-radius: 10px;
            line-height: 50px;
            color: rgba(134, 134, 134, 1);
          }
        }
      }
    }
    .btns {
      display: flex;
      justify-content: space-between;
      margin-top: 20px;
      .cancel {
        height: 40px;
        width: 48%;
        cursor: pointer;
        border-radius: 8px;
        background-color: rgba(240, 240, 240, 1);
        line-height: 40px;
        font-size: 14px;
      }
      .ok {
        height: 40px;
        width: 48%;
        cursor: pointer;
        border-radius: 8px;
        background-color: rgba(61, 104, 225, 1);
        color: #fff;
        line-height: 40px;
        font-size: 14px;
      }
    }
  .el-button--text {
    color: #3d68e1;
    text-decoration: underline;
  }
}
</style>
src/pages/systemSettings/components/CloudNode.vue
New file
@@ -0,0 +1,149 @@
<template>
  <div class="cloud">
    <div class="inner">
      <div class="rect">
        <serfDiagram
          ref="inside-nodes"
          :members="insideNodes"
          :agent="agentName"
          :sizeX="insideSizeX"
          :sizeY="insideSizeY"
          :startX="insideStartX"
          :isShowHover="true"
          class="inside-nodes"
        ></serfDiagram>
      </div>
    </div>
    <div class="outer" v-if="outsideNodes.length">
      <serfDiagram
        ref="outer-nodes"
        :members="outsideNodes"
        :agent="agentName"
        :sizeX="280"
        :sizeY="370"
        :startX="60"
        class="outer-nodes"
      ></serfDiagram>
    </div>
  </div>
</template>
<script>
import SerfDiagram from "@/components/serfDiagram";
export default {
  name: "cloudNode",
  props: {
    nodes: Array,
  },
  components: {
    SerfDiagram,
  },
  data() {
    return {
      agentName: "",
      nodeIcons: [],
      //insideNodes: [],
      BaseWidth: 150,
      BaseHeight: 70,
      minWidth: 0,
      minHeight: 0,
    };
  },
  mounted() {
    console.log(this.nodes);
    //this.getInsideNodes();
  },
  methods: {
    getRandom(index) {
      if (index % 2 == 0) {
        return Math.random() * 20;
      } else {
        return Math.random() * 50;
      }
    },
    getInsideNodes() {
      let arr = this.nodes.filter(
        (item) => item.hardwareType == "01" || item.hardwareType == "02"
      );
      let len = arr.length;
      let lefts = [];
      let tops = [];
      this.insideNodes = arr.map((item, index) => {
        lefts.push((20 - len) * (index + 1) + this.getRandom(index));
        tops.push(30 * (index + 1));
        return {
          l: 10 + this.getRandom(index),
          t: 30 * (index + 1),
          nodeName: item.nodeName,
          id: item.id,
          workType: item.workType,
        };
      });
      this.minWidth = Math.max(...lefts) - Math.min(...lefts);
      this.minHeight = Math.max(...tops) - Math.min(...tops);
      console.log("w,h", this.minWidth, this.minHeight);
    },
  },
  computed: {
    cloudPic() {
      return "/images/settings/cloud.png";
    },
    insideNodes() {
      return this.nodes.filter(
        (item) => item.hardwareType == "01" || item.hardwareType == "02"
      );
    },
    insideSizeX() {
      return 160 + 200 * 0.2 * this.insideNodes.length <= 400
        ? 160 + 200 * 0.2 * this.insideNodes.length
        : 400;
    },
    insideSizeY() {
      return 140 + 200 * 0.2 * this.insideNodes.length <= 380
        ? 140 + 200 * 0.2 * this.insideNodes.length
        : 380;
    },
    insideStartX() {
      return this.insideSizeX / 3;
    },
    outsideNodes() {
      return this.nodes.filter((item) => item.hardwareType == "03");
    },
  },
};
</script>
<style lang="scss">
.cloud {
  width: 100%;
  display: flex;
  .inner {
    background: url("/images/settings/easy-cloud.png") no-repeat;
    background-size: 100%;
    margin-top: -55px;
    margin-left: 55px;
    .rect {
      position: relative;
      margin: 130px 100px 70px;
      .node {
        position: absolute;
        .node-icon {
          width: 40px;
          height: 40px;
        }
        .node-name {
          font-size: 14px;
          color: #333;
        }
      }
    }
  }
  .outer {
    width: 40%;
    position: relative;
    text-align: left;
    .node {
      position: absolute;
    }
  }
}
</style>
src/pages/systemSettings/components/switchBar.vue
src/pages/systemSettings/index/App.vue
New file
@@ -0,0 +1,1831 @@
<template>
  <div class="container" v-if="!showWelcome">
    <div class="container-left">
      <div
        class="left-card"
        :class="activeIndex == index ? 'left-card-active' : ''"
        v-for="(item, index) in menuArr"
        :key="index"
        @click="openMenu(item.name, index)"
      >
        <span class="icon iconfont">&#xe646;</span>
        <span class="card-text">{{ item.name }}</span>
      </div>
    </div>
    <div
      class="container-center"
      v-if="activePage == '账户' || activePage == '日期时间'"
    >
      <div class="account-left" v-if="activePage == '账户'">
        <div class="account-list">
          <div
            class="account-card"
            :class="activeAccountIndex == index ? 'account-card-active' : ''"
            v-for="(item, index) in accountArr"
            :key="index"
            ref="account-card"
            @click="openAccount(item, index)"
          >
            <div class="touxiang">
              <img
                v-if="item.headpic"
                :src="`data:image/png;base64,${item.headpic}`"
                alt=""
              />
            </div>
            <span class="user-name">{{ item.username }}</span>
          </div>
        </div>
        <div class="add-account">
          <span class="icon iconfont" @click="showAddAccount">&#xe646;</span>
        </div>
      </div>
      <div class="datetime-left" v-if="activePage == '日期时间'">
        <div class="time-card">
          <div class="head">
            <span class="icon iconfont">&#xe690;</span>
            <span>设备时间</span>
          </div>
          <div class="time-main">{{ equipmentTime }}</div>
          <div class="date-bot">
            <span class="year">{{ equipmentDate }}</span>
            <span class="week">{{ weekday }}</span>
          </div>
        </div>
        <div class="line">
          <div class="name">NTP校时</div>
          <el-switch
            v-model="isNtp"
            @change="changeSwitch('isNtp')"
            active-color="rgba(61, 104, 225, 1)"
          >
          </el-switch>
        </div>
        <div class="line">
          <div class="name">手动校对</div>
          <el-switch
            v-model="isManual"
            @change="changeSwitch('isManual')"
            active-color="rgba(61, 104, 225, 1)"
          >
          </el-switch>
        </div>
      </div>
    </div>
    <div
      class="container-right"
      v-if="activePage == '账户' || activePage == '日期时间'"
    >
      <div class="account-right" v-if="activePage == '账户'">
        <div
          class="account-content"
          v-if="inAccountDetail == false && isAddAccount == false"
        >
          <div class="content-top">
            <div class="touxiang-big">
              <img
                v-if="activeAccountItem.headpic"
                :src="`data:image/png;base64,${activeAccountItem.headpic}`"
                alt=""
              />
            </div>
            <div class="user-desc">
              <div class="username">
                <span class="icon iconfont" style="margin-right: 5px"
                  >&#xe690;</span
                >
                <span>{{ activeAccountItem.username }}</span>
              </div>
              <div class="nickname">
                <span>昵称:</span>
                <span v-show="!showInputNickName">{{
                  activeAccountItem.nickname
                }}</span>
                <input
                  type="text"
                  class="input-nick"
                  ref="input-nick"
                  v-show="showInputNickName"
                  v-model="inputNickName"
                  @blur="hideInputNick"
                  @keydown.enter="blurInputNick"
                />
                <span class="icon iconfont" @click="editNickName"
                  >&#xe60c;</span
                >
              </div>
              <div class="user-role">
                {{
                  activeAccountItem.sysRoles.length
                    ? activeAccountItem.sysRoles[0].name
                    : ""
                }}
              </div>
            </div>
          </div>
          <div class="list-btn">
            <div class="item-btn" @click="showChangePassword">修改密码</div>
            <div class="item-btn" @click="deleteAccount">删除账户</div>
            <div class="item-btn" @click="openPermission">权限设置</div>
          </div>
        </div>
        <div class="change-pw" v-if="inAccountDetail && isChangePw">
          <div class="title">修改密码</div>
          <el-form
            :model="passwordForm"
            :rules="pwRules"
            ref="passwordForm"
            class="password-form"
          >
            <el-form-item prop="curPassword">
              <div class="p-title">当前密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.curPassword"
                show-password
              ></el-input>
            </el-form-item>
            <el-form-item prop="newPassword">
              <div class="p-title">新密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.newPassword"
                show-password
              ></el-input>
            </el-form-item>
            <el-form-item prop="confirmPassword">
              <div class="p-title">确认密码:</div>
              <el-input
                placeholder="必填"
                v-model="passwordForm.confirmPassword"
                show-password
              ></el-input>
            </el-form-item>
          </el-form>
          <div class="btns">
            <div class="cancel" @click="cancelPassword">取消</div>
            <div class="ok" @click="SaveNewPassword('passwordForm')">保存</div>
          </div>
        </div>
        <div class="permission" v-if="inAccountDetail && isSetPermission">
          <div class="title">权限管理</div>
          <div class="line-wrap" v-for="item in sysMenus" :key="item.id">
            <div class="line">
              <div class="name">{{ item.name }}</div>
              <el-switch
                v-model="item.selected"
                active-color="rgba(61, 104, 225, 1)"
                @change="fatherChange(item)"
              >
              </el-switch>
            </div>
            <div v-if="item.children">
              <div
                class="line"
                style="margin-left: 55px"
                v-for="x in item.children"
                :key="x.id"
              >
                <div class="name">{{ x.name }}</div>
                <el-switch
                  v-model="x.selected"
                  active-color="rgba(61, 104, 225, 1)"
                  @change="childrenChange(item)"
                >
                </el-switch>
              </div>
            </div>
          </div>
          <div class="btns">
            <div class="cancel" @click="cancelSet">取消</div>
            <div class="ok" @click="saveAuth">保存</div>
          </div>
        </div>
        <div class="add-account-page" v-if="isAddAccount">
          <div class="title">添加账户</div>
          <div class="upload-group">
            <div
              class="upload-jpg"
              :class="
                selectedPic == index? 'upload-jpg-border' : ''
              "
              v-for="(item, index) in jpgArr"
              :key="index"
              @click="pickHeadDefPic(item, index)"
            >
              <img
                v-if="item"
                :src="`data:image/png;base64,${item.path}`"
                alt=""
                srcset=""
              />
              <div class="img-mask" v-if="selectedPic == index">
                <span class="icon iconfont enable">&#xe62a;</span>
              </div>
            </div>
            <!-- <el-upload
              class="upload-demo"
              action="https://jsonplaceholder.typicode.com/posts/"
              :show-file-list="false"
              :http-request="uploadUserPic"
            >
              <div v-if="loadedPic == ''" class="upload-jpg-up">上传</div>
            </el-upload> -->
          </div>
          <div class="fill-group">
            <el-form
              :model="addForm"
              :rules="rules"
              ref="addForm"
              class="add-form"
            >
              <el-form-item prop="userName">
                <div class="p-title">用户名:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.userName"
                ></el-input>
              </el-form-item>
              <el-form-item prop="roleId">
                <div class="p-title">角色:</div>
                <el-select v-model="addForm.roleId" placeholder="请选择角色">
                  <el-option
                    v-for="(item, i) in roleList"
                    :key="i"
                    :label="item.name"
                    :value="item.id"
                  ></el-option>
                </el-select>
              </el-form-item>
              <el-form-item prop="nickName">
                <div class="p-title">昵称:</div>
                <el-input
                  placeholder="选填"
                  v-model="addForm.nickName"
                ></el-input>
              </el-form-item>
              <el-form-item prop="password">
                <div class="p-title">密码:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.password"
                  show-password
                ></el-input>
              </el-form-item>
              <el-form-item prop="confirmPassword">
                <div class="p-title">确认密码:</div>
                <el-input
                  placeholder="必填"
                  v-model="addForm.confirmPassword"
                  show-password
                ></el-input>
              </el-form-item>
            </el-form>
          </div>
          <div class="btns">
            <div class="cancel" @click="cancelAdd">取消</div>
            <div class="ok" @click="saveAddAccount('addForm')">保存</div>
          </div>
        </div>
      </div>
      <div class="datetime-right" v-if="activePage == '日期时间'">
        <div class="ntp-time" v-if="isNtp">
          <el-form label-width="160px">
            <el-form-item label="服务器地址">
              <!-- :disabled="syncType === '2'" -->
              <ip-input
                :ip="ntpServer"
                @on-blur="ntpServer = arguments[0]"
              ></ip-input>
            </el-form-item>
            <el-form-item label="校时时间间隔(分钟)">
              <div class="right">
                <el-input-number
                  v-model.number="timeInterval"
                  :min="1"
                  :max="60"
                  placeholder="请输入"
                  size="small"
                  :controls="false"
                ></el-input-number>
                <el-button
                  type="text"
                  @click="testNTP"
                  :loading="ntpTestLoading"
                  >测试</el-button
                >
              </div>
            </el-form-item>
          </el-form>
        </div>
        <div class="manual-time" v-if="isManual">
          <switchBar
            :barName="`同步本计算机时间`"
            @switchChange="syncBrowser"
            :value="isSyncBrowser"
          ></switchBar>
          <div class="clock-wrap">
            <div class="clock">
              <div class="hour">
                <div class="dnum" @click="showInput('Hour')">
                  <span v-show="!showHourInput">{{ syncHour }}</span>
                  <input
                    class="input-box"
                    v-show="showHourInput"
                    ref="iptHour"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>23)value='23'"
                    type="text"
                    v-model="inputHour"
                    @blur="hideInput('Hour')"
                    @keydown.enter="hideInput('Hour')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('hrs')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('hrs')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
              <div class="sep">:</div>
              <div class="mins">
                <div class="dnum" @click="showInput('Min')">
                  <span v-show="!showMinInput">{{ syncMin }}</span>
                  <input
                    class="input-box"
                    v-show="showMinInput"
                    ref="iptMin"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>59)value='59'"
                    type="text"
                    v-model="inputMin"
                    @blur="hideInput('Min')"
                    @keydown.enter="hideInput('Min')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('min')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('min')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
              <div class="sep">:</div>
              <div class="mins">
                <div class="dnum" @click="showInput('Sec')">
                  <span v-show="!showSecInput">{{ syncSec }}</span>
                  <input
                    class="input-box"
                    v-show="showSecInput"
                    ref="iptSec"
                    oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>59)value='59'"
                    type="text"
                    v-model="inputSec"
                    @blur="hideInput('Sec')"
                    @keydown.enter="hideInput('Sec')"
                  />
                </div>
                <div class="control">
                  <span class="icon iconfont" @click="plusOne('sec')"
                    >&#xe60e;</span
                  >
                  <span class="icon iconfont fanzhuan" @click="minusOne('sec')"
                    >&#xe60e;</span
                  >
                </div>
              </div>
            </div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('yrs')">-</div>
            <div class="middle" @click="showInput('Yrs')">
              <span v-show="!showYrsInput">{{ syncYrs }}</span>
              <input
                class="input-box"
                v-show="showYrsInput"
                ref="iptYrs"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>4)value=value.slice(0,4);"
                type="text"
                v-model="inputYrs"
                @blur="hideInput('Yrs')"
                @keydown.enter="hideInput('Yrs')"
              />
              年
            </div>
            <div class="plus" @click="plusOne('yrs')">+</div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('mth')">-</div>
            <div class="middle" @click="showInput('Month')">
              <span v-show="!showMonthInput">{{ syncMonth }}</span>
              <input
                class="input-box"
                v-show="showMonthInput"
                ref="iptMonth"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>12)value='12'"
                type="text"
                v-model="inputMonth"
                @blur="hideInput('Month')"
                @keydown.enter="hideInput('Month')"
              />
              月
            </div>
            <div class="plus" @click="plusOne('mth')">+</div>
          </div>
          <div class="adjust-bar">
            <div class="minus" @click="minusOne('day')">-</div>
            <div class="middle" @click="showInput('Day')">
              <span v-show="!showDayInput">{{ syncDay }}</span>
              <input
                class="input-box"
                v-show="showDayInput"
                ref="iptDay"
                oninput="value=value.replace(/[^\d]/g,'');if(value.length>2)value=value.slice(0,2);if(+value>31)value='31'"
                type="text"
                v-model="inputDay"
                @blur="hideInput('Day')"
                @keydown.enter="hideInput('Day')"
              />
              日
            </div>
            <div class="plus" @click="plusOne('day')">+</div>
          </div>
        </div>
        <div class="btns">
          <div class="cancel" @click="cancelPassword">取消</div>
          <div class="ok" @click="submitClock">保存</div>
        </div>
      </div>
    </div>
    <clusterManagement
      v-if="activePage == '集群管理'"
      style="width: 100%"
    ></clusterManagement>
    <netSettings
      v-if="activePage == '网络设置'"
      style="width: 100%"
    ></netSettings>
    <keyboardLanguage
      v-if="activePage == '键盘和语言'"
      style="width: 100%"
    ></keyboardLanguage>
    <generalSettings
      v-if="activePage == '通用设置'"
      style="width: 100%"
    ></generalSettings>
  </div>
  <div class="welcome-page" v-else>
    <div class="child" @click="openWelcome('账户',0)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6de;</span>
        <span class="welcome-title">账户</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('日期时间',1)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6ff;</span>
        <span class="welcome-title">日期时间</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('集群管理',2)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6df;</span>
        <span class="welcome-title">集群管理</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('网络设置',3)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6dd;</span>
        <span class="welcome-title">网络设置</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('键盘和语言',4)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6dc;</span>
        <span class="welcome-title">键盘和语言</span>
      </div>
    </div>
    <div class="child"  @click="openWelcome('通用设置',5)">
      <div class="child-info">
        <span class="icon iconfont welcome-icon">&#xe6db;</span>
        <span class="welcome-title">通用设置</span>
      </div>
    </div>
  </div>
</template>
<script>
import { getClockInfo, saveClockInfo, testNTPserver } from "@/api/system";
import {
  uploadHeadPic,
  addUser,
  getUsers,
  updateUser,
  updataUser,
  updatePassword,
  deleteUser,
  getUserMenus,
  defHeadPics,
  getRoles,
} from "@/api/user";
import switchBar from "../components/switchBar";
import ipInput from "@/components/subComponents/IPInput";
import clusterManagement from "../views/clusterManagement";
import netSettings from "../views/NetSettings";
import keyboardLanguage from "../views/keyboardLanguage";
import generalSettings from "../views/generalSettings";
export default {
  name: "settings",
  components: {
    switchBar,
    ipInput,
    clusterManagement,
    netSettings,
    keyboardLanguage,
    generalSettings,
  },
  data() {
    var validatePass2 = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.addForm.password) {
        callback(new Error("两次输入密码不一致!"));
      } else {
        callback();
      }
    };
    var validatePass4 = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请再次输入密码"));
      } else if (value !== this.passwordForm.newPassword) {
        callback(new Error("两次输入密码不一致!"));
      } else {
        callback();
      }
    };
    return {
      showAccount: true,
      syncYrs: "",
      activeAccountItem: {},
      syncMonth: "",
      syncHour: "",
      showWelcome: true,
      syncDay: "",
      syncMin: "",
      syncSec: "00",
      isSyncBrowser: false,
      showDateTime: false,
      isAddAccount: false,
      isCount: false,
      isCalculate: false,
      browserTimer: null,
      timezone: "",
      isRealtime: false,
      inputNickName: "",
      showHourInput: false,
      showMinInput: false,
      showSecInput: false,
      showYrsInput: false,
      showMonthInput: false,
      selectedPic: null,
      showDayInput: false,
      loadedPic: "",
      cameraInfo: false,
      dependentScene: false,
      timestamp: 0,
      inAccountDetail: false,
      isChangePw: false,
      isSetPermission: false,
      timeInterval: 10,
      ntpServer: "",
      syncType: "1",
      equipmentTime: "",
      equipmentDate: "",
      roleList: [],
      ntpTestLoading: false,
      settime: "",
      weekday: "",
      menuArr: [
        { name: "账户" },
        { name: "日期时间" },
        { name: "集群管理" },
        { name: "网络设置" },
        { name: "键盘和语言" },
        { name: "通用设置" },
      ],
      accountArr: [],
      jpgArr: [],
      isManual: false,
      isNtp: true,
      activePage: "账户",
      activeIndex: 0,
      clockTimer: null,
      inputHour: "",
      inputMin: "",
      inputSec: "",
      inputYrs: "",
      showInputNickName: false,
      inputMonth: "",
      inputDay: "",
      passwordForm: {
        curPassword: "",
        newPassword: "",
        confirmPassword: "",
      },
      activeAccountIndex: 0,
      sysMenus: [],
      addForm: {
        userName: "",
        nickName: "",
        password: "",
        headpic: "",
        confirmPassword: "",
        roleId: "",
      },
      rules: {
        userName: [
          { required: true, message: "请输入用户名", trigger: "blur" },
          {
            min: 3,
            max: 10,
            message: "长度在 3 到 10 个字符",
            trigger: "blur",
          },
        ],
        password: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        confirmPassword: [{ validator: validatePass2, trigger: "blur" }],
      },
      pwRules: {
        // pw: [{ validator: validatePass3, trigger: "blur" }],
        curPassword: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        newPassword: [
          { required: true, message: "请输入密码", trigger: "blur" },
          { min: 6, message: "长度至少为 6 位", trigger: "blur" },
        ],
        confirmPassword: [{ validator: validatePass4, trigger: "blur" }],
      },
    };
  },
  beforeDestroy() {
    clearTimeout(this.clockTimer);
    clearInterval(this.browserTimer);
  },
  mounted() {
    this.fetchUserList();
    this.fetchDefHeadPic();
  },
  methods: {
    fetchDefHeadPic() {
      defHeadPics().then((res) => {
        this.jpgArr = res.data;
      });
    },
    fetchUserList(showLast = false) {
      getUsers().then((res) => {
        this.accountArr = res.data;
        if (this.accountArr.length) {
          this.activeAccountItem = this.accountArr[0];
          this.activeAccountIndex = 0;
        }
        if (showLast) {
          this.cancelAdd();
          const lastIdx = this.accountArr.length - 1;
          this.openAccount(this.accountArr[lastIdx], lastIdx);
        }
      });
    },
    blurInputNick() {
      this.$refs["input-nick"].blur();
    },
    editNickName() {
      this.showInputNickName = true;
      this.inputNickName = this.activeAccountItem.nickname;
      this.$refs["input-nick"].focus();
    },
    hideInputNick() {
      this.showInputNickName = false;
      updateUser({
        id: this.activeAccountItem.id,
        nickname: this.inputNickName,
      }).then((res) => {
        this.$message.success(res.msg);
        this.fetchUserList(true);
      });
    },
    openAccount(item, i) {
      this.activeAccountItem = item;
      this.activeAccountIndex = i;
      this.inAccountDetail = false;
      this.cancelSet();
      this.fetchMenu();
    },
    minusOne(typ) {
      this.isSyncBrowser = false;
      this.syncBrowser(false);
      let num;
      switch (typ) {
        case "hrs":
          num = +this.syncHour - 1;
          if (num == -1) {
            num = 23;
          }
          this.syncHour = this.padZero(num);
          break;
        case "min":
          num = +this.syncMin - 1;
          if (num == -1) {
            num = 59;
          }
          this.syncMin = this.padZero(num);
          break;
        case "sec":
          num = +this.syncSec + 1;
          if (num == -1) {
            num = 59;
          }
          this.syncSec = this.padZero(num);
          break;
        case "yrs":
          num = +this.syncYrs - 1;
          this.syncYrs = this.padZero(num);
          break;
        case "mth":
          num = +this.syncMonth - 1;
          if (num == 0) {
            num = 12;
            this.minusOne("yrs");
          }
          this.syncMonth = this.padZero(num);
          break;
        case "day":
          num = +this.syncDay - 1;
          if (num == 0) {
            this.minusOne("mth");
            const maxDay = new Date(
              +this.syncYrs,
              +this.syncMonth,
              0
            ).getDate();
            num = maxDay;
          }
          this.syncDay = this.padZero(num);
          break;
        default:
          break;
      }
    },
    fatherChange(item) {
      item.children.forEach((x) => {
        x.selected = item.selected;
      });
    },
    childrenChange(item) {
      let isAllSelected = item.children.every((x) => x.selected == true);
      item.selected = isAllSelected;
    },
    plusOne(typ) {
      this.isSyncBrowser = false;
      this.syncBrowser(false);
      let num;
      switch (typ) {
        case "hrs":
          num = +this.syncHour + 1;
          if (num == 24) {
            num = 0;
          }
          this.syncHour = this.padZero(num);
          break;
        case "min":
          num = +this.syncMin + 1;
          if (num == 60) {
            num = 0;
          }
          this.syncMin = this.padZero(num);
          break;
        case "sec":
          num = +this.syncSec + 1;
          if (num == 60) {
            num = 0;
          }
          this.syncSec = this.padZero(num);
          break;
        case "yrs":
          num = +this.syncYrs + 1;
          this.syncYrs = this.padZero(num);
          break;
        case "mth":
          num = +this.syncMonth + 1;
          if (num == 13) {
            num = 1;
          }
          this.syncMonth = this.padZero(num);
          break;
        case "day":
          num = +this.syncDay + 1;
          const maxDay = new Date(+this.syncYrs, +this.syncMonth, 0).getDate();
          if (num > maxDay) {
            num = 1;
          }
          this.syncDay = this.padZero(num);
          break;
        default:
          break;
      }
    },
    submitClock() {
      if (this.syncType === "1") {
        if (this.ntpServer === "" || this.ntpServer === "...") {
          this.$notify({
            type: "error",
            message: "NTP 服务器地址不能为空",
          });
          return false;
        } else if (this.timeInterval === "") {
          this.timeInterval = 1;
        }
      } else if (this.isSyncBrowser) {
        if (this.settime === "") {
          this.$notify({
            type: "error",
            message: "设置时间不能为空",
          });
          return false;
        }
      } else {
        this.settime = `${this.syncYrs}-${this.syncMonth}-${this.syncDay} ${this.syncHour}:${this.syncMin}:${this.syncSec}`;
      }
      saveClockInfo({
        timeZone: this.timezone,
        ntp: this.syncType === "1",
        ntpServer: this.ntpServer,
        interval: this.timeInterval,
        newTime: this.settime,
      }).then((rsp) => {
        if (rsp && rsp.success) {
          this.$notify({
            type: "success",
            message: "设置成功",
          });
        }
        this.initClockConf();
      });
    },
    flatGetArr(arr, res) {
      for (const item of arr) {
        if (item.selected) res.push(item.id);
        if (item.children) this.flatGetArr(item.children, res);
      }
    },
    saveAuth() {
      let arr = [];
      this.flatGetArr(this.sysMenus, arr);
      updataUser({
        id: this.activeAccountItem.id,
        menuIds: arr,
      }).then((res) => {
        if (res.success) {
          this.$message.success(res.msg);
          this.cancelSet();
        }
      });
    },
    formatTime(number, format) {
      var formateArr = ["Y", "M", "D", "h", "m", "s"];
      var returnArr = [];
      var date = new Date(number * 1000);
      returnArr.push(date.getFullYear());
      returnArr.push(this.formatNumber(date.getMonth() + 1));
      returnArr.push(this.formatNumber(date.getDate()));
      returnArr.push(this.formatNumber(date.getHours()));
      returnArr.push(this.formatNumber(date.getMinutes()));
      returnArr.push(this.formatNumber(date.getSeconds()));
      this.weekday = "星期" + "日一二三四五六".charAt(date.getDay());
      for (var i in returnArr) {
        format = format.replace(formateArr[i], returnArr[i]);
      }
      return format;
    },
    padZero(n) {
      n = +n;
      return n < 10 ? "0" + n : "" + n;
    },
    formatNumber(n) {
      n = n.toString();
      return n[1] ? n : "0" + n;
    },
    // uploadUserPic(params) {
    //   let param = new FormData();
    //   param.append("file", params.file);
    //   uploadHeadPic(param).then((res) => {
    //     this.jpgArr.push(res.data);
    //     this.loadedPic = res.data;
    //   });
    // },
    initClockConf(ntpTest = false) {
      getClockInfo().then((rsp) => {
        if (rsp && rsp.success) {
          this.timezone = rsp.data.time_zone;
          if (!ntpTest) {
            this.syncType = rsp.data.ntp ? "1" : "2";
            this.isNtp = rsp.data.ntp;
            this.isManual = !rsp.data.ntp;
          }
          if (rsp.data.ntp) {
            this.ntpServer = rsp.data.ntp_server;
            this.timeInterval = rsp.data.interval;
          }
          this.timestamp = rsp.data.local_time;
          if (this.clockTimer === null) {
            this.runClock();
            if (this.isManual) this.parseTime();
          }
        }
      });
    },
    openMenu(name, i) {
      this.activePage = name;
      this.activeIndex = i;
      if (this.activePage == "日期时间") {
        this.initClockConf();
      }
    },
    openWelcome(name,i){
      this.showWelcome = false
      this.openMenu(name,i)
    },
    showInput(typ) {
      this[`show${typ}Input`] = true;
      this.$nextTick(() => {
        this.$refs[`ipt${typ}`].focus();
      });
    },
    hideInput(typ) {
      if (this[`input${typ}`]) {
        this[`sync${typ}`] = this.padZero(this[`input${typ}`]);
      }
      this[`show${typ}Input`] = false;
      this[`input${typ}`] = "";
    },
    syncBrowser(enable) {
      this.isSyncBrowser = enable;
      if (!enable) {
        clearInterval(this.browserTimer);
      } else {
        this.browserTimer = setInterval(() => {
          let timestamp = new Date().getTime() / 1000;
          this.settime = this.formatTime(timestamp, "Y-M-D h:m:s");
          let [arr1, arr2] = this.settime.split(" ");
          [this.syncYrs, this.syncMonth, this.syncDay] = arr1.split("-");
          [this.syncHour, this.syncMin, this.syncSec] = arr2.split(":");
        }, 1000);
      }
    },
    showAddAccount() {
      this.inAccountDetail = false;
      this.isAddAccount = true;
      getRoles().then((res) => {
        if (res.success) {
          this.roleList = res.data;
        }
      });
    },
    cancelAdd() {
      this.inAccountDetail = false;
      this.isAddAccount = false;
      this.$refs["addForm"].resetFields();
      this.selectedPic = null;
    },
    cancelPassword() {
      this.isChangePw = false;
      this.inAccountDetail = false;
      this.$refs["passwordForm"].resetFields();
    },
    SaveNewPassword(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          updatePassword({
            oldPwd: this.passwordForm.curPassword,
            newPwd: this.passwordForm.newPassword,
          }).then(
            (res) => {
              if (res.success) {
                this.$message.success(res.msg);
                this.cancelPassword();
              }
            },
            (err) => {
              this.$message.warning("保存失败," + err.msg);
            }
          );
        }
      });
    },
    saveAddAccount(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          let data = {
            username: this.addForm.userName,
            password: this.addForm.password,
            nickname: this.addForm.nickName,
            headpic: this.addForm.headpic,
          };
          addUser(data).then(
            (res) => {
              this.$message.success(res.data);
              this.fetchUserList(true);
            },
            (err) => {
              this.$message.warning("保存失败," + err.msg);
            }
          );
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    pickHeadDefPic(item, i) {
      this.addForm.headpic = item.path;
      this.selectedPic = i;
    },
    testNTP() {
      this.ntpTestLoading = true;
      testNTPserver({ server: this.ntpServer })
        .then((rsp) => {
          if (rsp && rsp.success) {
            this.$notify({
              type: "success",
              message: "时间同步成功",
            });
          } else {
            this.$notify({
              type: "error",
              message: "时间同步失败",
            });
          }
          this.ntpTestLoading = false;
          this.initClockConf(true);
        })
        .catch((err) => {
          this.$notify({
            type: "error",
            message: "时间同步失败,请检查服务器ip",
          });
          this.ntpTestLoading = false;
        });
    },
    cancelSet() {
      this.isSetPermission = false;
      this.inAccountDetail = false;
      this.sysMenus = [];
    },
    showChangePassword() {
      this.isChangePw = true;
      this.inAccountDetail = true;
    },
    runClock() {
      const str = this.formatTime(++this.timestamp, "Y-M-D h:m:s");
      [this.equipmentDate, this.equipmentTime] = str.split(" ");
      this.clockTimer = setTimeout(() => {
        this.runClock();
      }, 1000);
    },
    deleteAccount() {
      this.$confirm("您是否确认删除账户?", "删除账户", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
      })
        .then(() => {
          let obj = {
            ids: [this.activeAccountItem.id],
          };
          deleteUser(obj).then((res) => {
            if (res.success) {
              this.fetchUserList();
              this.$message({
                type: "success",
                message: "删除成功!",
              });
            } else {
              this.$message.warning("删除失败");
            }
          });
        })
        .catch(() => {});
    },
    fetchMenu() {
      getUserMenus({
        userId: this.activeAccountItem.id,
      }).then((res) => {
        if (res && res.success) {
          this.sysMenus = res.data;
        }
      });
    },
    openPermission() {
      this.inAccountDetail = true;
      this.isSetPermission = true;
      if (this.sysMenus.length == 0) {
        this.fetchMenu();
      }
    },
    parseTime() {
      [this.syncYrs, this.syncMonth, this.syncDay] = this.equipmentDate.split(
        "-"
      );
      [this.syncHour, this.syncMin, this.syncSec] = this.equipmentTime.split(
        ":"
      );
    },
    changeSwitch(str) {
      if (str == "isNtp") {
        this.isManual = !this[str];
      } else {
        this.isNtp = !this[str];
      }
      this.syncType = this.isNtp ? "1" : "2";
      if (this.isManual) this.parseTime();
    },
  },
  computed: {
    isShowAddAccount() {
      const info = JSON.parse(sessionStorage.getItem("userInfo"));
      return true;
    },
  },
};
</script>
<style lang="scss">
.welcome-page {
  width: 100%;
  height: 100%;
  background-color: #f5f5f5;
  display: -ms-flexbox;
  padding: 0 50px;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  .child {
    box-sizing: border-box;
    background-color: white;
    -webkit-box-flex: 0;
    -ms-flex: 0 0 33.3%;
    /* flex: 0 0 16%; */
    float: left;
    width: 250px;
    height: 200px;
    margin: 50px 57px 30px 57px;
    border-radius: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    .child-info {
      display: flex;
      flex-direction: column;
      height: 62%;
      justify-content: space-around;
      .welcome-icon {
        font-size: 72px;
      }
      .welcome-title {
        font-size: 18px;
      }
    }
  }
  .child:hover {
    -moz-box-shadow: 5px 5px 10px #ddd;
    -webkit-box-shadow: 5px 5px 10px #ddd;
    box-shadow: 5px 5px 10px #ddd;
    transform: translate3d(0,-2.5px,0);
     transition: all 0.3s;
}
}
.container {
  height: 100%;
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-basis: auto;
  box-sizing: border-box;
  .container-left {
    height: 100%;
     width: 230px;
    overflow: auto;
    box-sizing: border-box;
    flex-shrink: 0;
    padding: 10px;
    border-right: 5px solid rgba(248, 248, 248, 1);
    box-sizing: border-box;
    .left-card {
      height: 55px;
      cursor: pointer;
      border-radius: 12px;
      margin-bottom: 10px;
      display: flex;
      align-items: center;
      .iconfont {
        margin-left: 25px;
        margin-right: 10px;
        font-size: 24px;
      }
      .card-text {
        font-size: 16px;
      }
    }
    .left-card-active {
      background-color: rgba(61, 104, 225, 1);
      color: #fff;
    }
    .left-card:hover {
      background-color: rgba(61, 104, 225, 1);
      color: #fff;
    }
  }
  .container-center {
    height: 100%;
    width: 280px;
    overflow: auto;
    flex-shrink: 0;
    padding: 10px;
    border-right: 5px solid rgba(248, 248, 248, 1);
    box-sizing: border-box;
    .account-left {
      .add-account {
        color: rgba(61, 104, 225, 1);
        margin-top: 30px;
        .iconfont {
          cursor: pointer;
          font-size: 32px;
        }
      }
      .account-card {
        height: 50px;
        background-color: rgba(248, 248, 248, 1);
        margin-bottom: 10px;
        display: flex;
        align-items: center;
        padding: 0 20px;
        box-sizing: border-box;
        border-radius: 10px;
        cursor: pointer;
        .touxiang {
          height: 35px;
          width: 35px;
          background-color: bisque;
          border-radius: 17.5px;
          img {
            border: none;
            height: 35px;
            width: 35px;
            border-radius: 17.5px;
          }
        }
        .user-name {
          margin-left: 10px;
          font-size: 14px;
        }
      }
      .account-card-active {
        background-color: rgba(61, 104, 225, 1);
        color: #fff;
      }
    }
    .datetime-left {
      .time-card {
        height: 105px;
        background-color: rgba(248, 248, 248, 1);
        margin-bottom: 30px;
        border-radius: 10px;
        .head {
          height: 30px;
          line-height: 30px;
          text-align: left;
          box-sizing: border-box;
          padding: 0 10px;
          font-size: 14px;
          .icon {
            margin-right: 5px;
            color: rgba(61, 104, 225, 1);
          }
        }
        .time-main {
          height: 42px;
          line-height: 42px;
          font-family: Consolas;
          font-size: 36px;
        }
        .date-bot {
          height: 25px;
          font-size: 14px;
          line-height: 25px;
          color: #868686;
          display: flex;
          justify-content: space-evenly;
        }
      }
      .line {
        display: flex;
        align-items: center;
        height: 50px;
        padding: 0 25px;
        background-color: rgba(248, 248, 248, 1);
        justify-content: space-between;
        border-radius: 12px;
        margin-bottom: 10px;
        .name {
          font-size: 14px;
        }
      }
    }
  }
  .container-right {
    flex: 1;
    flex-basis: auto;
    overflow: auto;
    box-sizing: border-box;
    position: relative;
    padding: 20px 40px;
    .account-right {
      .account-content {
        .content-top {
          height: 120px;
          width: 350px;
          margin: 0 auto;
          display: flex;
          align-items: center;
          justify-content: center;
          margin-bottom: 20px;
          .touxiang-big {
            width: 100px;
            height: 100px;
            background-color: bisque;
            border-radius: 50px;
            img {
              border: none;
              width: 100px;
              height: 100px;
              border-radius: 50px;
            }
          }
          .user-desc {
            height: 100px;
            display: flex;
            flex-direction: column;
            align-items: baseline;
            min-width: 200px;
            .username {
              margin: 5px 15px;
              height: 30px;
              line-height: 30px;
              // width: 90px;
              text-align: left;
              font-size: 15px;
              display: flex;
              align-items: center;
            }
            .nickname {
              margin: 5px 15px;
              font-size: 14px;
              .input-nick {
                width: 50px;
                margin-right: 5px;
              }
              .iconfont {
                font-size: 14px;
                margin-left: 5px;
                cursor: pointer;
              }
            }
            .user-role {
              margin: 5px 0 0 15px;
              font-size: 14px;
              color: darkseagreen;
            }
          }
        }
        .list-btn {
          display: flex;
          flex-direction: column;
          align-items: center;
          .item-btn {
            width: 500px;
            height: 45px;
            background-color: #f0f0f0;
            margin-bottom: 15px;
            border-radius: 10px;
            line-height: 45px;
            font-size: 15px;
            cursor: pointer;
          }
          .item-btn:hover {
            color: rgba(255, 153, 102, 1);
          }
        }
      }
      .title {
        height: 30px;
        line-height: 30px;
        /* background-color: aliceblue; */
        margin-bottom: 10px;
        font-size: 16px;
        font-weight: 600;
      }
      .change-pw {
        .p-title {
          text-align: left;
          font-size: 15px;
          margin-top: 5px;
        }
      }
      .el-form-item {
        margin-bottom: 0;
        .el-input__inner {
          background-color: rgba(240, 240, 240, 1);
          border: none;
          border-radius: 12px;
          height: 45px;
          padding: 0 20px;
          font-size: 15px;
        }
        .el-input__clear {
          color: dimgray;
          font-size: 17px;
          line-height: 45px;
        }
        .el-input__suffix {
          right: 1px;
          top: -0.5px;
          width: 45px;
          // background-color: rgba(61, 104, 225, 1);
          /* color: white; */
          border-radius: 12px;
        }
      }
      .permission {
        .line {
          display: flex;
          align-items: center;
          height: 50px;
          padding: 0 25px;
          background-color: rgba(248, 248, 248, 1);
          justify-content: space-between;
          border-radius: 12px;
          margin-bottom: 10px;
          .name {
            font-size: 14px;
          }
        }
      }
      .add-account-page {
        .upload-group {
          height: 120px;
          width: 360px;
          margin: 0 auto;
          overflow: hidden;
          .upload-jpg {
            position: relative;
            height: 48px;
            width: 48px;
            float: left;
            margin: 0 10px;
            margin-bottom: 10px;
            background-color: rgba(242, 242, 242, 1);
            border: 2px solid transparent;
            border-radius: 50%;
            cursor: pointer;
            img {
              height: 48px;
              width: 48px;
              border-radius: 50%;
            }
            .img-mask {
              position: absolute;
              left: 0;
              top: 0;
              height: 48px;
              width: 48px;
              background-color: rgba(0, 0, 0, 0.3);
              color: white;
              border-radius: 50%;
              display: flex;
              justify-content: center;
              align-items: center;
              .selected {
                font-size: 22px;
              }
            }
          }
          .upload-jpg-border {
            border: 2px solid cornflowerblue;
          }
          .upload-jpg-up {
            height: 48px;
            width: 48px;
            float: left;
            display: flex;
            background-color: rgba(242, 242, 242, 1);
            border: 2px solid transparent;
            border-radius: 50%;
            justify-content: center;
            align-items: center;
            font-size: 12px;
            cursor: pointer;
          }
        }
        .fill-group {
          .p-title {
            height: 34px;
            text-align: left;
          }
          .el-form-item {
            .el-select {
              width: 100%;
            }
          }
        }
      }
    }
    .datetime-right {
      .el-form-item.is-required:not(.is-no-asterisk)
        > .el-form-item__label:before,
      .el-form-item.is-required:not(.is-no-asterisk)
        .el-form-item__label-wrap
        > .el-form-item__label:before {
        display: none;
      }
      .el-form-item {
        margin-bottom: 10px;
        height: 50px;
        background: #f8f8f8;
        padding: 4px 20px;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        border-radius: 10px;
        .el-form-item__label {
          text-align: left;
          line-height: 42px;
        }
      }
      .el-form-item__content {
        line-height: 40px;
        position: relative;
        font-size: 14px;
        margin-bottom: 10px;
      }
      .ip-input-container {
        max-width: none !important;
      }
      .ntp-time {
        .right {
          display: flex;
          align-items: baseline;
          .el-input-number--small {
            width: 100%;
          }
          .el-button--text {
            margin-left: 10px;
            text-decoration: underline;
          }
        }
        .ntp-bar {
          height: 40px;
          background-color: rgba(248, 248, 248, 1);
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: 0 10px;
          border-radius: 10px;
          margin-bottom: 10px;
          .title {
            min-width: 70px;
          }
          .input-area {
            width: 450px;
            height: 30px;
            background-color: rgba(240, 240, 240, 1);
            border-radius: 10px;
            line-height: 30px;
            font-size: 14px;
          }
        }
        .int-bar {
          height: 40px;
          background-color: rgba(248, 248, 248, 1);
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: 0 10px;
          border-radius: 10px;
          margin-bottom: 10px;
          .title {
            min-width: 130px;
          }
          .right {
            width: 450px;
            display: flex;
            align-items: center;
            height: 30px;
            .input-area {
              // width: 410px;
              background-color: rgba(240, 240, 240, 1);
              border-radius: 10px;
              line-height: 30px;
              width: -webkit-fill-available;
              font-size: 14px;
            }
            .test {
              width: 40px;
            }
          }
        }
      }
      .manual-time {
        .clock-wrap {
          height: 75px;
          background-color: #f8f8f8;
          display: flex;
          justify-content: center;
          align-items: center;
          margin-bottom: 10px;
          border-radius: 10px;
          .clock {
            display: flex;
            align-items: center;
            height: 90px;
            justify-content: space-evenly;
            .iconfont {
              cursor: pointer;
              color: rgba(134, 134, 134, 1);
            }
            .iconfont:hover {
              background-color: gainsboro;
            }
            .hour {
              background-color: rgba(240, 240, 240, 1);
              display: flex;
              align-items: center;
              width: 100px;
              height: 50px;
              justify-content: space-evenly;
              border-radius: 10px;
            }
            .dnum {
              width: 40px;
              height: 40px;
              line-height: 40px;
              font-size: 34px;
              font-family: Consolas;
              display: flex;
              align-items: center;
              .input-box {
                width: inherit;
                border: none;
                border-radius: 5px;
                height: 35px;
                font-size: 28px;
                text-align: center;
              }
              .input-box:focus {
                outline: none;
              }
            }
            .control {
              width: 20px;
              .fanzhuan {
                display: inline-block;
                -moz-transform: scaleY(-1);
                -webkit-transform: scaleY(-1);
                -o-transform: scaleY(-1);
                transform: scaleY(-1);
              }
            }
            .sep {
              font-family: Consolas;
              width: 40px;
              font-size: 34px;
              height: 40px;
              line-height: 40px;
            }
            .mins {
              background-color: #f0f0f0;
              display: flex;
              align-items: center;
              width: 110px;
              height: 50px;
              justify-content: space-evenly;
              border-radius: 10px;
            }
          }
        }
        .adjust-bar {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 10px;
          border-radius: 10px;
          height: 50px;
          background-color: rgba(248, 248, 248, 1);
          .middle {
            font-size: 14px;
          }
          .input-box {
            width: 80px;
            border: none;
            border-radius: 5px;
            height: 25px;
            font-size: 18px;
            text-align: center;
          }
          .input-box:focus {
            outline: none;
          }
          .minus {
            width: 50px;
            height: 50px;
            background-color: #f0f0f0;
            font-size: 35px;
            border-radius: 10px;
            cursor: pointer;
            line-height: 50px;
            color: rgba(134, 134, 134, 1);
          }
          .plus {
            width: 50px;
            height: 50px;
            cursor: pointer;
            background-color: #f0f0f0;
            font-size: 35px;
            border-radius: 10px;
            line-height: 50px;
            color: rgba(134, 134, 134, 1);
          }
        }
      }
    }
    .btns {
      display: flex;
      justify-content: space-between;
      margin-top: 20px;
      .cancel {
        height: 40px;
        width: 48%;
        cursor: pointer;
        border-radius: 8px;
        background-color: rgba(240, 240, 240, 1);
        line-height: 40px;
        font-size: 14px;
      }
      .ok {
        height: 40px;
        width: 48%;
        cursor: pointer;
        border-radius: 8px;
        background-color: rgba(61, 104, 225, 1);
        color: #fff;
        line-height: 40px;
        font-size: 14px;
      }
    }
  }
}
</style>
src/pages/systemSettings/index/main.ts
New file
@@ -0,0 +1,12 @@
import Vue from 'vue';
import App from './App.vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import "@/assets/css/element-variables.scss";
Vue.use(ElementUI)
new Vue({
  el: '#app',
  render: h => h(App)
})
src/pages/systemSettings/views/NetSettings.vue
src/pages/systemSettings/views/clusterManagement.vue
src/pages/systemSettings/views/generalSettings.vue
src/pages/systemSettings/views/keyboardLanguage.vue