568 lines
20 KiB
Diff
568 lines
20 KiB
Diff
From 4ab8ff449fd0ae87de58a64509f1218eaf0a1efd Mon Sep 17 00:00:00 2001
|
||
From: wkl505997900 <505997900@qq.com>
|
||
Date: Mon, 24 Apr 2023 16:11:22 +0800
|
||
Subject: [PATCH] Verify the host name field before and after adding a host
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
---
|
||
.../ant-design-pro/layouts/UserLayout.vue | 3 +-
|
||
src/views/assests/HostEdition.vue | 80 ++++++++++---------
|
||
src/views/assests/HostManagement.vue | 24 +++---
|
||
src/views/assests/components/EditableCell.vue | 48 ++++++++---
|
||
src/views/assests/components/addMoreHost.vue | 54 ++++++++++---
|
||
src/views/leaks/components/UploadFile.vue | 4 +-
|
||
src/views/user/Login.vue | 2 +-
|
||
src/views/user/Register.vue | 6 +-
|
||
8 files changed, 145 insertions(+), 76 deletions(-)
|
||
|
||
diff --git a/src/vendor/ant-design-pro/layouts/UserLayout.vue b/src/vendor/ant-design-pro/layouts/UserLayout.vue
|
||
index 72dc176..b054097 100644
|
||
--- a/src/vendor/ant-design-pro/layouts/UserLayout.vue
|
||
+++ b/src/vendor/ant-design-pro/layouts/UserLayout.vue
|
||
@@ -158,7 +158,8 @@ export default {
|
||
}
|
||
|
||
.footer {
|
||
- width: 100%;
|
||
+ position: absolute;
|
||
+ width: 98%;
|
||
bottom: 0;
|
||
padding: 0 16px;
|
||
margin: 155px 0 24px;
|
||
diff --git a/src/views/assests/HostEdition.vue b/src/views/assests/HostEdition.vue
|
||
index fac76fc..a945a68 100644
|
||
--- a/src/views/assests/HostEdition.vue
|
||
+++ b/src/views/assests/HostEdition.vue
|
||
@@ -12,10 +12,10 @@
|
||
:maxLength="50"
|
||
v-decorator="[
|
||
'host_name',
|
||
- {rules: [{ required: true, message: '请输入主机名称'}, {validator: checkNameInput}]}
|
||
+ {rules: [{ required: true, message: '请输入主机名称'}, {validator: checkNameInput, validateTrigger: 'blur'}]},
|
||
]"
|
||
placeholder="请输入主机名称,50个字符以内">
|
||
- <a-tooltip slot="suffix" title="最大长度50个字符,由数字、小写字母、英文下划线_组成。以小写字母开头,且结尾不能是英文下划线_">
|
||
+ <a-tooltip slot="suffix" title="最大长度50个字符,首尾不能为空格,不允许全空格">
|
||
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
|
||
</a-tooltip>
|
||
</a-input>
|
||
@@ -64,6 +64,7 @@
|
||
<a-input-number
|
||
:min="0"
|
||
:max="65535"
|
||
+ @change="handlePortChange"
|
||
v-decorator="[
|
||
'ssh_port',
|
||
{initialValue: 22, rules: [{required: true, message: '请输入 0~65535 内正整数'}]}
|
||
@@ -80,6 +81,7 @@
|
||
</a-form-item>
|
||
<a-form-item label="主机用户名">
|
||
<a-input
|
||
+ @change="handleUserChange"
|
||
v-decorator="[
|
||
'ssh_user',
|
||
{rules: [{required: true, message: '请输入主机用户名'}]}
|
||
@@ -94,25 +96,10 @@
|
||
<a-input-password
|
||
v-decorator="[
|
||
'password',
|
||
- {rules: [{required: pageType === 'create' ? true : false, message: '请输入主机登录密码'}]}
|
||
+ {rules: [{required: pageType === 'create' ? true : requiredRules, message: '请输入主机登录密码'}]}
|
||
]"
|
||
- placeholder="请设置登录密码, 若为空则不修改"></a-input-password>
|
||
+ :placeholder="pageType === 'create' ? '请设置主机登录密码' : '请输入主机登陆密码, 若未修改主机用户名或端口可以为空'"></a-input-password>
|
||
</a-form-item>
|
||
- <!-- <a-form-item label="主机sudo密码">
|
||
- <a-input-password
|
||
- v-decorator="[
|
||
- 'sudo_password',
|
||
- {rules: [{required: true, message: '请输入主机sudo密码'}, {validator: passwordCheck}]}
|
||
- ]"
|
||
- placeholder="请设置sudo密码,长度8-20个字符"
|
||
- />
|
||
- </a-form-item> -->
|
||
- <!-- <a-form-item label="加密密钥">
|
||
- <a-input-password
|
||
- v-decorator="['key', {rules: [{required: true, message: '请输入加密密钥'}, {validator: passwordCheck}]}]"
|
||
- placeholder="请设置用于给主机私密信息加密的密钥,长度8-20个字符"
|
||
- />
|
||
- </a-form-item> -->
|
||
<a-form-item :wrapper-col="{span: 10, offset: 5}">
|
||
<a-button @click="handleCancel">取消</a-button>
|
||
<a-button
|
||
@@ -158,7 +145,9 @@ export default {
|
||
hostGroupList: [],
|
||
hostGroupIsLoading: false,
|
||
form: this.$form.createForm(this),
|
||
- submitLoading: false
|
||
+ submitLoading: false,
|
||
+ PortRequired: false,
|
||
+ UserRequired: false
|
||
};
|
||
},
|
||
computed: {
|
||
@@ -183,6 +172,10 @@ export default {
|
||
}
|
||
};
|
||
},
|
||
+ requiredRules() {
|
||
+ // 当前为修改页面,只要端口号或主机用户名有一个改变时,密码为必须项
|
||
+ return this.UserRequired || this.PortRequired
|
||
+ },
|
||
...mapState({
|
||
hostInfo: (state) => state.host.hostInfo
|
||
})
|
||
@@ -200,6 +193,16 @@ export default {
|
||
}
|
||
},
|
||
methods: {
|
||
+ handleUserChange(value) {
|
||
+ if (this.pageType === 'edit') {
|
||
+ value.target.value === this.basicHostInfo.ssh_user ? this.UserRequired = false : this.UserRequired = true
|
||
+ }
|
||
+ },
|
||
+ handlePortChange(value) {
|
||
+ if (this.pageType === 'edit') {
|
||
+ value === this.basicHostInfo.ssh_port ? this.PortRequired = false : this.PortRequired = true
|
||
+ }
|
||
+ },
|
||
// 获取主机组列表数据
|
||
getHostGroupList() {
|
||
const _this = this;
|
||
@@ -240,18 +243,23 @@ export default {
|
||
}
|
||
}
|
||
}
|
||
- editHost(tableParams, this.hostId)
|
||
- .then(function (res) {
|
||
- _this.$message.success(res.message);
|
||
- store.dispatch('resetHostInfo');
|
||
- router.push('/assests/hosts-management');
|
||
- })
|
||
- .catch(function (err) {
|
||
- _this.$message.error(err.response.message);
|
||
- })
|
||
- .finally(function () {
|
||
- _this.submitLoading = false;
|
||
- });
|
||
+ if (JSON.stringify(tableParams) === '{}') {
|
||
+ this.$message.info('未存在修改数据!')
|
||
+ this.submitLoading = false;
|
||
+ } else {
|
||
+ editHost(tableParams, this.hostId)
|
||
+ .then(function (res) {
|
||
+ _this.$message.success(res.message);
|
||
+ store.dispatch('resetHostInfo');
|
||
+ router.push('/assests/hosts-management');
|
||
+ })
|
||
+ .catch(function (err) {
|
||
+ _this.$message.error(err.response.message);
|
||
+ })
|
||
+ .finally(function () {
|
||
+ _this.submitLoading = false;
|
||
+ });
|
||
+ }
|
||
} else {
|
||
addHost(values)
|
||
.then(function (res) {
|
||
@@ -278,15 +286,15 @@ export default {
|
||
router.go(-1);
|
||
},
|
||
checkNameInput(rule, value, cb) {
|
||
- if (/[^0-9a-z_.]/.test(value)) {
|
||
+ if (!/^\S.*\S$/.test(value)) {
|
||
/* eslint-disable */
|
||
- cb('只能输入数字、小写字母和英文.和_');
|
||
+ cb('首尾不允许空格');
|
||
/* eslint-enable */
|
||
return;
|
||
}
|
||
- if (/[_]$/.test(value)) {
|
||
+ if (!/^(?!\s*$).+/.test(value)) {
|
||
/* eslint-disable */
|
||
- cb('结尾不能是英文下划线');
|
||
+ cb('不允许全空格');
|
||
/* eslint-enable */
|
||
return;
|
||
}
|
||
diff --git a/src/views/assests/HostManagement.vue b/src/views/assests/HostManagement.vue
|
||
index 81c25eb..080c145 100644
|
||
--- a/src/views/assests/HostManagement.vue
|
||
+++ b/src/views/assests/HostManagement.vue
|
||
@@ -58,7 +58,7 @@
|
||
slot="hostName"
|
||
slot-scope="hostName, record">{{ hostName }}</router-link>
|
||
<span slot="isManagement" slot-scope="isMana">{{ isMana ? '是' : '否' }}</span>
|
||
- <span slot="statusItem" slot-scope="status">{{ statusMap(status) }}</span>
|
||
+ <span slot="statusItem" slot-scope="status">{{ hostStatusMap[status] }}</span>
|
||
<span slot="scene" slot-scope="scene">{{ scene ? ( scene === 'normal' ? '通用' : scene ) : '暂无' }}</span>
|
||
<span slot="action" slot-scope="record">
|
||
<!-- <a @click="openDetail(record.host_id)">查看</a>
|
||
@@ -89,9 +89,17 @@ import MyPageHeaderWrapper from '@/views/utils/MyPageHeaderWrapper';
|
||
import {getSelectedRow} from '@/views/utils/getSelectedRow';
|
||
import HostDetailDrawer from './components/HostDetailDrawer';
|
||
// import HostTerminal from '@/views/assests/components/HostTerminal';
|
||
-
|
||
import {hostList, deleteHost, hostGroupList} from '@/api/assest';
|
||
|
||
+const hostStatusMap = {
|
||
+ '0': '在线',
|
||
+ '1': '离线',
|
||
+ '2': '未确认',
|
||
+ '3': '扫描中',
|
||
+ '4': '已完成',
|
||
+ '5': '未知'
|
||
+};
|
||
+
|
||
const defaultPagination = {
|
||
current: 1,
|
||
pageSize: 10,
|
||
@@ -110,6 +118,7 @@ export default {
|
||
},
|
||
data() {
|
||
return {
|
||
+ hostStatusMap,
|
||
rowKey: 'host_id',
|
||
pagination: defaultPagination,
|
||
filters: null,
|
||
@@ -218,17 +227,6 @@ export default {
|
||
handleUploadSuccess() {
|
||
this.getHostList();
|
||
},
|
||
- statusMap(params) {
|
||
- let status = '';
|
||
- if (params === 1) {
|
||
- status = '离线';
|
||
- } else if (params === 2) {
|
||
- status = '未连接';
|
||
- } else {
|
||
- status = '在线';
|
||
- }
|
||
- return status;
|
||
- },
|
||
onSelectChange(selectedRowKeys, selectedRows) {
|
||
this.selectedRowKeys = selectedRowKeys;
|
||
this.selectedRowsAll = getSelectedRow(selectedRowKeys, this.selectedRowsAll, this.tableData, 'host_id');
|
||
diff --git a/src/views/assests/components/EditableCell.vue b/src/views/assests/components/EditableCell.vue
|
||
index 102908a..282ba8c 100644
|
||
--- a/src/views/assests/components/EditableCell.vue
|
||
+++ b/src/views/assests/components/EditableCell.vue
|
||
@@ -1,5 +1,5 @@
|
||
<template>
|
||
- <div class="editable-cell">
|
||
+ <div class="editable-cell" ref="childitem">
|
||
<a-form-model
|
||
ref="ruleForm"
|
||
:model="form"
|
||
@@ -7,7 +7,9 @@
|
||
>
|
||
<div v-if="editable" class="editable-cell-input-wrapper">
|
||
<a-form-model-item :prop="formkey">
|
||
- <a-input @change="handleChange" @pressEnter="check" v-model="form[formkey]" />
|
||
+ <!-- 当formkey为密码时,使用密码框组件 -->
|
||
+ <a-input-password v-if="formkey === 'password'" @change="handleChange" @pressEnter="check" v-model="form[formkey]" />
|
||
+ <a-input v-else @change="handleChange" @pressEnter="check" v-model="form[formkey]" />
|
||
<a-icon
|
||
style="top: -7px;"
|
||
type="check"
|
||
@@ -19,7 +21,7 @@
|
||
<div v-else class="editable-cell-text-wrapper">
|
||
<div class="editable-content">
|
||
<!-- <a-input :type="formkey === 'password' ? 'password' : 'text'" v-model="value" /> -->
|
||
- <span v-if="formkey === 'password'">******</span>
|
||
+ <span v-if="formkey === 'password'">{{ countStar(form[formkey]) }}</span>
|
||
<span v-else>{{ value || ' ' }}</span>
|
||
</div>
|
||
<a-icon type="edit" class="editable-cell-icon" @click="edit" />
|
||
@@ -58,15 +60,15 @@ export default {
|
||
}
|
||
};
|
||
const checkNameInput = (rule, value, callback) => {
|
||
- if (/[^0-9a-z_.]/.test(value)) {
|
||
+ if (!/^\S.*\S$/.test(value)) {
|
||
/* eslint-disable */
|
||
- callback(new Error('只能输入数字、小写字母和英文.和_'));
|
||
+ callback(new Error('首尾不允许空格'));
|
||
/* eslint-enable */
|
||
return;
|
||
}
|
||
- if (/[_]$/.test(value)) {
|
||
+ if (!/^(?!\s*$).+/.test(value)) {
|
||
/* eslint-disable */
|
||
- callback(new Error('结尾不能是英文下划线'));
|
||
+ callback(new Error('不允许全空格'));
|
||
/* eslint-enable */
|
||
return;
|
||
}
|
||
@@ -102,6 +104,13 @@ export default {
|
||
};
|
||
},
|
||
methods: {
|
||
+ countStar(num) {
|
||
+ let str = ''
|
||
+ for (let i = 0; i < num.length; i++) {
|
||
+ str += '*'
|
||
+ }
|
||
+ return str
|
||
+ },
|
||
handleChange(e) {
|
||
const value = e.target.value;
|
||
this.value = value;
|
||
@@ -113,22 +122,39 @@ export default {
|
||
check() {
|
||
this.$refs.ruleForm.validate(valid => {
|
||
if (valid) {
|
||
- this.editable = false;
|
||
- this.$emit('change', this.value);
|
||
+ if (this.editable) {
|
||
+ // 判断当前状态,只对处于修改状态的组件执行此操作,节省性能
|
||
+ this.editable = false;
|
||
+ this.$emit('allowSub')
|
||
+ this.$emit('change', this.value);
|
||
+ }
|
||
} else {
|
||
return false;
|
||
}
|
||
});
|
||
},
|
||
edit() {
|
||
+ this.$emit('unSubmit')
|
||
this.editable = true;
|
||
+ },
|
||
+ handleClickOutside(event) {
|
||
+ // 鼠标监听事件
|
||
+ const target = event.target
|
||
+ const wrapper = this.$refs.childitem
|
||
+ // 判断点击的区域是否是当前组件的区域
|
||
+ if (!wrapper.contains(target)) {
|
||
+ // 当点击组件之外时 执行校验操作
|
||
+ this.check()
|
||
+ }
|
||
}
|
||
},
|
||
created() {
|
||
},
|
||
+ beforeDestroy() {
|
||
+ document.removeEventListener('mouseup', this.handleClickOutside)
|
||
+ },
|
||
mounted() {
|
||
- // this.edit()
|
||
- // this.check()
|
||
+ document.addEventListener('mouseup', this.handleClickOutside)
|
||
},
|
||
computed: {
|
||
form () {
|
||
diff --git a/src/views/assests/components/addMoreHost.vue b/src/views/assests/components/addMoreHost.vue
|
||
index 830bf03..4cd9a65 100644
|
||
--- a/src/views/assests/components/addMoreHost.vue
|
||
+++ b/src/views/assests/components/addMoreHost.vue
|
||
@@ -32,7 +32,8 @@
|
||
ref="host_ip"
|
||
formkey="host_ip"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'host_ip', $event)" />
|
||
</template>
|
||
<template slot="ssh_port" slot-scope="text, record">
|
||
@@ -40,7 +41,8 @@
|
||
ref="ssh_port"
|
||
formkey="ssh_port"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'ssh_port', $event)" />
|
||
</template>
|
||
<template slot="ssh_user" slot-scope="text, record">
|
||
@@ -48,7 +50,8 @@
|
||
ref="ssh_user"
|
||
formkey="ssh_user"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'ssh_user', $event)" />
|
||
</template>
|
||
<template slot="password" slot-scope="text, record">
|
||
@@ -56,7 +59,8 @@
|
||
ref="password"
|
||
formkey="password"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'password', $event)" />
|
||
</template>
|
||
<template slot="host_name" slot-scope="text, record">
|
||
@@ -64,7 +68,8 @@
|
||
ref="host_name"
|
||
formkey="host_name"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'host_name', $event)" />
|
||
</template>
|
||
<template slot="host_group_name" slot-scope="text, record">
|
||
@@ -72,7 +77,8 @@
|
||
ref="host_group_name"
|
||
formkey="host_group_name"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'host_group_name', $event)" />
|
||
</template>
|
||
<template slot="management" slot-scope="text, record">
|
||
@@ -80,7 +86,8 @@
|
||
ref="management"
|
||
formkey="management"
|
||
:text="String(text)"
|
||
- @uploadstatus="uploadstatus($event)"
|
||
+ @unSubmit="unSubmit()"
|
||
+ @allowSub="allowSub()"
|
||
@change="onCellChange(record.key, 'management', $event)" />
|
||
</template>
|
||
<template slot="operation" slot-scope="text, record">
|
||
@@ -108,7 +115,7 @@
|
||
<div style="display: flex;justify-content: flex-end;">
|
||
<a-button
|
||
type="primary"
|
||
- :disabled="fileDataList.length === 0 || tableData.length === 0"
|
||
+ :disabled="fileDataList.length === 0 || tableData.length === 0 || isSubDisable"
|
||
:loading="uploading"
|
||
style="margin-top: 16px;width: 111px;"
|
||
@click="goUpload">
|
||
@@ -133,6 +140,7 @@ export default {
|
||
props: {},
|
||
data() {
|
||
return {
|
||
+ editNum: 0,
|
||
dataAllow: true,
|
||
count: '',
|
||
rowKey: 'ip',
|
||
@@ -156,6 +164,13 @@ export default {
|
||
};
|
||
},
|
||
computed: {
|
||
+ isSubDisable() {
|
||
+ if (this.editNum > 0) {
|
||
+ return true
|
||
+ } else {
|
||
+ return false
|
||
+ }
|
||
+ },
|
||
columns() {
|
||
return [
|
||
{
|
||
@@ -224,7 +239,12 @@ export default {
|
||
}
|
||
},
|
||
methods: {
|
||
- uploadstatus(value) {},
|
||
+ unSubmit() {
|
||
+ this.editNum++;
|
||
+ },
|
||
+ allowSub() {
|
||
+ this.editNum--;
|
||
+ },
|
||
onCellChange(key, dataIndex, value) {
|
||
const dataSource = [...this.tableData];
|
||
const target = dataSource.find((item) => item.key === key);
|
||
@@ -413,6 +433,22 @@ export default {
|
||
})
|
||
_this.$message.error('全部主机添加失败!')
|
||
} else {
|
||
+ if (err.response.code === '1000') {
|
||
+ const errorList = [];
|
||
+ const errorData = {};
|
||
+ err.response.data.forEach((item) => {
|
||
+ errorList.push(item.host_ip);
|
||
+ errorData[item.host_ip] = item.reason
|
||
+ });
|
||
+ _this.tableData.forEach((item) => {
|
||
+ if (errorList.includes(item.host_ip)) {
|
||
+ item.result = '添加失败'
|
||
+ }
|
||
+ if (Object.keys(errorData).includes(item.host_ip)) {
|
||
+ item.reason = errorData[item.host_ip]
|
||
+ }
|
||
+ })
|
||
+ }
|
||
_this.$message.error(err.response.message || err.response.data.detail);
|
||
}
|
||
})
|
||
diff --git a/src/views/leaks/components/UploadFile.vue b/src/views/leaks/components/UploadFile.vue
|
||
index c4e072a..fc2c8a6 100644
|
||
--- a/src/views/leaks/components/UploadFile.vue
|
||
+++ b/src/views/leaks/components/UploadFile.vue
|
||
@@ -17,7 +17,7 @@
|
||
</a-upload>
|
||
</div>
|
||
<div style="margin-top: 14px;font-size: 15px;">
|
||
- <a-radio-group name="radioGroup" v-model="value" :default-value="1" @change="onChange">
|
||
+ <a-radio-group name="radioGroup" v-model="value" :default-value="2" @change="onChange">
|
||
<a-radio :value="1">
|
||
不受影响
|
||
</a-radio>
|
||
@@ -43,7 +43,7 @@ export default {
|
||
props: {},
|
||
data() {
|
||
return {
|
||
- value: 1,
|
||
+ value: 2,
|
||
fileDataList: [],
|
||
visible: false,
|
||
uploading: false
|
||
diff --git a/src/views/user/Login.vue b/src/views/user/Login.vue
|
||
index fae311a..a956fe0 100644
|
||
--- a/src/views/user/Login.vue
|
||
+++ b/src/views/user/Login.vue
|
||
@@ -162,7 +162,7 @@ export default {
|
||
}
|
||
|
||
.jump_registar {
|
||
- margin-left: 245px;
|
||
+ text-align: right;
|
||
margin-top: -20px;
|
||
.spin_top_jump {
|
||
color:#005980;
|
||
diff --git a/src/views/user/Register.vue b/src/views/user/Register.vue
|
||
index 1b2c170..08205b4 100644
|
||
--- a/src/views/user/Register.vue
|
||
+++ b/src/views/user/Register.vue
|
||
@@ -63,7 +63,7 @@
|
||
<a-input
|
||
size="large"
|
||
type="text"
|
||
- placeholder="输入由英文字母、数字、下划线、英文句号、以及中划线组成的邮箱地址"
|
||
+ placeholder="输入邮箱地址,由英文字母、数字、下划线、英文句号、以及中划线组成"
|
||
v-decorator="[
|
||
'email',
|
||
{
|
||
@@ -72,7 +72,7 @@
|
||
}
|
||
]"
|
||
>
|
||
- <a-icon slot="prefix" type="user" :style="{color: 'rgba(0,0,0,.25)'}" />
|
||
+ <a-icon slot="prefix" type="mail" :style="{color: 'rgba(0,0,0,.25)'}" />
|
||
</a-input>
|
||
</a-form-item>
|
||
|
||
@@ -233,7 +233,7 @@ export default {
|
||
}
|
||
|
||
.jump_login {
|
||
- margin-left: 245px;
|
||
+ text-align: right;
|
||
margin-top: -20px;
|
||
.spin_top_jump {
|
||
color:#005980;
|
||
--
|
||
Gitee
|
||
|