Commit 4e465235 by wanglizhen

规则管理模块上传

parent 6568eb5f
...@@ -116,3 +116,33 @@ export function delSecretKey(data) { ...@@ -116,3 +116,33 @@ export function delSecretKey(data) {
data: data data: data
}) })
} }
/**
* 加密规则 - 备份导出
* @param {*} data
* @returns
*/
export function exportExcel(data) {
return request({
url: '/core/encryption/export',
method: 'post',
data: data,
responseType: 'blob'
})
}
/**
* 加密规则 - 导入恢复
* @param {*} data
* @returns
*/
export function importfile(data) {
return request({
url: '/core/encryption/importfile',
method: 'post',
data: data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ import { queryFunctionList, test } from "@/api/ruleConfig/algorithm"; ...@@ -5,7 +5,7 @@ import { queryFunctionList, test } from "@/api/ruleConfig/algorithm";
const props = defineProps<{ const props = defineProps<{
modelValue: boolean; modelValue: boolean;
rulename?: String; editor?: Object;
}>(); }>();
const emit = defineEmits(["update:modelValue", "confirm", "cancel"]); const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
...@@ -27,8 +27,8 @@ const confirm = () => { ...@@ -27,8 +27,8 @@ const confirm = () => {
}; };
const cancel = () => { const cancel = () => {
editorValue.value = JSON.parse(JSON.stringify(props.rulename)); editorName.value = JSON.parse(JSON.stringify(props.editor.rulename));
editorName.value = JSON.parse(JSON.stringify(props.rulename)); editorValue.value = JSON.parse(JSON.stringify(props.editor.expression));
emit("cancel"); emit("cancel");
}; };
...@@ -73,10 +73,10 @@ watch( ...@@ -73,10 +73,10 @@ watch(
); );
watch( watch(
() => props.rulename, () => props.editor,
(newVal) => { (newVal) => {
editorValue.value = JSON.parse(JSON.stringify(props.rulename)); editorName.value = JSON.parse(JSON.stringify(props.editor.rulename));
editorName.value = JSON.parse(JSON.stringify(props.rulename)); editorValue.value = JSON.parse(JSON.stringify(props.editor.expression));
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
......
...@@ -62,7 +62,7 @@ const confirm = () => { ...@@ -62,7 +62,7 @@ const confirm = () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 999; z-index: 999999;
.modal-container { .modal-container {
background: #f3f5fa; background: #f3f5fa;
border: 0; border: 0;
......
...@@ -36,11 +36,9 @@ const modalPopShow = ref(false) ...@@ -36,11 +36,9 @@ const modalPopShow = ref(false)
// 获取数据 // 获取数据
const getCollapse = (type) => { const getCollapse = (type) => {
query({ dataarea: searchValue.value }).then(res => { query({ dataarea: searchValue.value }).then(res => {
rulenameList.value = []
const { data } = res const { data } = res
collapseList.value = data.map(item => { collapseList.value = data.map(item => {
const list = item.ruleList.map(itemTwo => { const list = item.ruleList.map(itemTwo => {
rulenameList.value.push(itemTwo.rulename)
return { return {
...itemTwo, ...itemTwo,
name: itemTwo.rulename, name: itemTwo.rulename,
...@@ -83,6 +81,10 @@ const collapseAdd = (item) => { ...@@ -83,6 +81,10 @@ const collapseAdd = (item) => {
addForm.value = { addForm.value = {
dataarea_id: id dataarea_id: id
} }
rulenameList.value = []
item.list.forEach(itemLi => {
rulenameList.value.push(itemLi.rulename)
})
modalPopShow.value = true modalPopShow.value = true
} }
...@@ -115,8 +117,13 @@ const modalConfirm = () => { ...@@ -115,8 +117,13 @@ const modalConfirm = () => {
} }
// 点击监听 // 点击监听
const collapseChange = (item) => { const collapseChange = (itemData) => {
editForm.value = item.item rulenameList.value = []
const { parentItem, item } = itemData
parentItem.list.forEach(itemLi => {
rulenameList.value.push(itemLi.rulename)
})
editForm.value = item
} }
// 算法确认 // 算法确认
...@@ -178,7 +185,7 @@ onMounted(() => { ...@@ -178,7 +185,7 @@ onMounted(() => {
<!-- 新增算法 --> <!-- 新增算法 -->
<ModalPop v-model="modalPopShow" title="新增算法" @cancel="modalPopCancel"> <ModalPop v-model="modalPopShow" title="新增算法" @cancel="modalPopCancel">
<template #content> <template #content>
<formModule :itemData="addForm" :nameList="rulenameList" type="add" @cancel="modalPopCancel" <formModule v-if="modalPopShow" :itemData="addForm" :nameList="rulenameList" type="add" @cancel="modalPopCancel"
@confirm="formModuleConfirm" /> @confirm="formModuleConfirm" />
</template> </template>
</ModalPop> </ModalPop>
......
...@@ -38,7 +38,10 @@ const { form, rules } = toRefs(data); ...@@ -38,7 +38,10 @@ const { form, rules } = toRefs(data);
const editorShow = ref(false); const editorShow = ref(false);
const readOnly = ref(true); const readOnly = ref(true);
const editor = ref({}); const editor = ref({
rulename: '',
expression: ''
});
const rulenameList = ref([]) const rulenameList = ref([])
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
...@@ -56,7 +59,10 @@ const reset = () => { ...@@ -56,7 +59,10 @@ const reset = () => {
// 打开编辑器 // 打开编辑器
const openEditor = () => { const openEditor = () => {
editorShow.value = true; editorShow.value = true;
editor.value = form.value.expression; editor.value = {
rulename: form.value.rulename,
expression: form.value.expression
};
}; };
// 取消 // 取消
...@@ -101,10 +107,6 @@ const saveFunc = () => { ...@@ -101,10 +107,6 @@ const saveFunc = () => {
// 算法名称验证 // 算法名称验证
const validateRulename = (rule, value, callback) => { const validateRulename = (rule, value, callback) => {
if (form.value.id && form.value.rulename === value) {
callback();
return false;
}
const state = rulenameList.value.includes(value); const state = rulenameList.value.includes(value);
if (state) { if (state) {
callback(new Error('算法名称已存在')); callback(new Error('算法名称已存在'));
...@@ -130,6 +132,9 @@ watch( ...@@ -130,6 +132,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
reset();
}
if (props.itemData) { if (props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
...@@ -182,7 +187,7 @@ watch( ...@@ -182,7 +187,7 @@ watch(
<el-button type="primary" style="width: 150px;" @click="confirm" v-if="!readOnly">确认</el-button> <el-button type="primary" style="width: 150px;" @click="confirm" v-if="!readOnly">确认</el-button>
</div> </div>
<ExpressionEditor v-model="editorShow" :rulename="editor" @cancel=" editorShow = false" @confirm="formConfirm" /> <ExpressionEditor v-model="editorShow" :editor="editor" @cancel=" editorShow = false" @confirm="formConfirm" />
</div> </div>
</template> </template>
......
...@@ -160,7 +160,7 @@ onMounted(() => { ...@@ -160,7 +160,7 @@ onMounted(() => {
<div class="app-container__body"> <div class="app-container__body">
<Split v-model="splitNum"> <Split v-model="splitNum">
<template #left> <template #left>
<div class="demo-split-pane" style="padding: 0 38px 10px 0;width: 100%;overflow: auto;height: 100%;"> <div class="demo-split-pane" style="padding: 0 38px 10px 0;width: 100%;overflow: auto;height: 100%;display: flex;flex-direction: column;">
<el-input class="mb20" v-model="queryParams.dataarea" placeholder="加密算法搜索"> <el-input class="mb20" v-model="queryParams.dataarea" placeholder="加密算法搜索">
<template #suffix> <template #suffix>
<el-icon style="vertical-align: middle;cursor: pointer;" @click="getCollapse"> <el-icon style="vertical-align: middle;cursor: pointer;" @click="getCollapse">
...@@ -168,8 +168,10 @@ onMounted(() => { ...@@ -168,8 +168,10 @@ onMounted(() => {
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
<CollapseView :list="collapseList" @add="collapseAdd" @childDelete="collapseDelete" <el-scrollbar style="width: 100%;flex: 1;padding-right: 38px;">
<CollapseView :list="collapseList" @add="collapseAdd" @childDelete="collapseDelete"
@change="collapseChange" /> @change="collapseChange" />
</el-scrollbar>
</div> </div>
</template> </template>
<template #right> <template #right>
...@@ -197,7 +199,7 @@ onMounted(() => { ...@@ -197,7 +199,7 @@ onMounted(() => {
<!-- 新增加密算法 --> <!-- 新增加密算法 -->
<ModalPop v-model="modalPopShow" width="800px" title="新增加密算法" @cancel="modalPopCancel"> <ModalPop v-model="modalPopShow" width="800px" title="新增加密算法" @cancel="modalPopCancel">
<template #content> <template #content>
<formModule :itemData="addForm" :encryptionDict="encryptionList" type="add" @cancel="modalPopCancel" <formModule v-if="modalPopShow" :itemData="addForm" :encryptionDict="encryptionList" type="add" @cancel="modalPopCancel"
@confirm="formModuleConfirm" /> @confirm="formModuleConfirm" />
</template> </template>
</ModalPop> </ModalPop>
......
...@@ -98,7 +98,7 @@ const saveFunc = () => { ...@@ -98,7 +98,7 @@ const saveFunc = () => {
} else if (props.type === "add") { } else if (props.type === "add") {
reset() reset()
} }
emit("confirm"); emit("confirm", props.type );
} }
}); });
} }
...@@ -134,6 +134,9 @@ watch( ...@@ -134,6 +134,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
reset();
}
if (props.itemData) { if (props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
......
...@@ -15,7 +15,7 @@ const splitNum = ref(0.31) // 左右分割比例 ...@@ -15,7 +15,7 @@ const splitNum = ref(0.31) // 左右分割比例
const data = reactive({ const data = reactive({
queryParams: { queryParams: {
pageno: 1, pageno: 1,
pagesize: 10, pagesize: 20,
typecode: '', typecode: '',
typename: '', typename: '',
value: '', value: '',
...@@ -363,6 +363,9 @@ onMounted(() => { ...@@ -363,6 +363,9 @@ onMounted(() => {
background-color: #e9f5fe !important; background-color: #e9f5fe !important;
color: #2c9ef7; color: #2c9ef7;
} }
:deep(.el-table__cell){
height: 48px;
}
} }
} }
</style> </style>
\ No newline at end of file
...@@ -96,6 +96,9 @@ const getInitEditType = (id) => { ...@@ -96,6 +96,9 @@ const getInitEditType = (id) => {
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
reset();
}
if (props.type === "edit" && props.itemData) { if (props.type === "edit" && props.itemData) {
const { id } = JSON.parse(JSON.stringify(props.itemData)); const { id } = JSON.parse(JSON.stringify(props.itemData));
getInitEditType(id) getInitEditType(id)
......
...@@ -80,6 +80,9 @@ const addFunc = () => { ...@@ -80,6 +80,9 @@ const addFunc = () => {
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
reset();
}
if (props.itemData) { if (props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
......
...@@ -88,6 +88,7 @@ const addDomainClick = () => { ...@@ -88,6 +88,7 @@ const addDomainClick = () => {
const collapseAdd = (item) => { const collapseAdd = (item) => {
console.log('新增发现规则', item) console.log('新增发现规则', item)
const { id, dataarea } = item const { id, dataarea } = item
rulesForm.value = {}
rulesForm.value = { rulesForm.value = {
dataarea_id: id, dataarea_id: id,
type: dataarea type: dataarea
......
...@@ -101,6 +101,9 @@ watch( ...@@ -101,6 +101,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
form.value = {}
}
if (props.type === 'edit' && props.itemData) { if (props.type === 'edit' && props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
......
...@@ -219,6 +219,9 @@ watch( ...@@ -219,6 +219,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
reset();
}
if (props.itemData) { if (props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import { onMounted, ref, toRefs } from 'vue' import { onMounted, ref, toRefs } from 'vue'
import { Split } from 'view-ui-plus'; import { Split } from 'view-ui-plus';
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { parseTime } from '@/utils/ruoyi'
import CollapseView from '@/components/CollapseView/index.vue' import CollapseView from '@/components/CollapseView/index.vue'
import AddEncryption from './modules/AddEncryption.vue' // 加密表单 import AddEncryption from './modules/AddEncryption.vue' // 加密表单
import AddKey from './modules/AddKey.vue' // 密钥表单 import AddKey from './modules/AddKey.vue' // 密钥表单
import ModalPop from "@/components/EditPop/ModalPop.vue" import ModalPop from "@/components/EditPop/ModalPop.vue"
import UploadModule from './modules/UploadModule.vue' // 上传模块
import { query, initEncryptionTypeList, getDictinaryTypes, deleteType, delSecretKey } from '@/api/ruleConfig/encryption' import { query, initEncryptionTypeList, getDictinaryTypes, deleteType, delSecretKey, exportExcel, importfile } from '@/api/ruleConfig/encryption'
const splitNum = ref(0.31) // 左右分割比例 const splitNum = ref(0.31) // 左右分割比例
const collapseList = ref([]) // 左侧列表数据 const collapseList = ref([]) // 左侧列表数据
...@@ -193,10 +195,33 @@ const AddFormConfirm = (item) => { ...@@ -193,10 +195,33 @@ const AddFormConfirm = (item) => {
getCollapse(item) getCollapse(item)
} else if (modalPopData.type === 'key') { } else if (modalPopData.type === 'key') {
getCollapse(item) getCollapse(item)
} else if (modalPopData.type === 'upload') {
getCollapse()
} }
modalPopData.show = false modalPopData.show = false
} }
// 备份导出excel
const exportExcelFunc = () => {
const name = parseTime(new Date(), '{y}-{m}-{d}')
exportExcel({}).then(response => {
const blob = new Blob([response], { type: 'application/vnd.ms-excel,charset=utf-8' });
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `${name}加密规则.xls`
link.click()
URL.revokeObjectURL(link.href)
ElMessage.success('导出成功')
})
}
// 导入功能
const importClick = () => {
modalPopData.title = '上传文件'
modalPopData.type = 'upload'
modalPopData.show = true
}
onMounted(() => { onMounted(() => {
getCollapse() getCollapse()
getInitEncryptionTypeList() getInitEncryptionTypeList()
...@@ -213,10 +238,10 @@ onMounted(() => { ...@@ -213,10 +238,10 @@ onMounted(() => {
<div class="app-container__body"> <div class="app-container__body">
<Split v-model="splitNum"> <Split v-model="splitNum">
<template #left> <template #left>
<div class="demo-split-pane" style="padding: 0 38px 10px 0;width: 100%;overflow: auto;height: 100%;"> <div class="demo-split-pane" style="padding: 0 38px 10px 0;width: 100%;overflow: auto;height: 100%;display: flex;flex-direction: column;">
<div class="mb20" style="display: flex;flex-direction: row-reverse;"> <div class="mb20" style="display: flex;flex-direction: row-reverse;">
<el-button icon="Upload" type="success">恢复</el-button> <el-button icon="Upload" type="success" @click="importClick">恢复</el-button>
<el-button class="mr10" icon="Download" type="primary">备份</el-button> <el-button class="mr10" icon="Download" type="primary" @click="exportExcelFunc">备份</el-button>
</div> </div>
<div class="mb20" style="display: flex;align-items: center;justify-content: space-between;"> <div class="mb20" style="display: flex;align-items: center;justify-content: space-between;">
<el-input class="mr20" v-model="queryParams.encryption_name" placeholder="加密方式搜索"> <el-input class="mr20" v-model="queryParams.encryption_name" placeholder="加密方式搜索">
...@@ -228,8 +253,10 @@ onMounted(() => { ...@@ -228,8 +253,10 @@ onMounted(() => {
</el-input> </el-input>
<el-button type="primary" icon="Plus" @click="addDomainClick">新增加密</el-button> <el-button type="primary" icon="Plus" @click="addDomainClick">新增加密</el-button>
</div> </div>
<CollapseView :list="collapseList" @add="collapseAdd" @mainDeletion="collapseMainDeletion" <el-scrollbar style="width: 100%;flex: 1;padding-right: 38px;">
<CollapseView :list="collapseList" @add="collapseAdd" @mainDeletion="collapseMainDeletion"
@childDelete="collapseDelete" @change="collapseChange" @view="collapseView" /> @childDelete="collapseDelete" @change="collapseChange" @view="collapseView" />
</el-scrollbar>
</div> </div>
</template> </template>
<template #right> <template #right>
...@@ -258,12 +285,13 @@ onMounted(() => { ...@@ -258,12 +285,13 @@ onMounted(() => {
@confirm="modalConfirm"></Modal> @confirm="modalConfirm"></Modal>
<!-- 新增加密 / 密钥 --> <!-- 新增加密 / 密钥 -->
<ModalPop :width="'850'" v-model="modalPopData.show" :title="modalPopData.title" @cancel="modalPopCancel"> <ModalPop :width="modalPopData.type === 'upload' ? '600' : '850'" v-model="modalPopData.show" :title="modalPopData.title" @cancel="modalPopCancel">
<template #content> <template #content>
<AddEncryption :itemData="encryptionForm" :encryptionTypeDict="encryptionTypeList" :typeNameDict="typeNameList" <AddEncryption :itemData="encryptionForm" :encryptionTypeDict="encryptionTypeList" :typeNameDict="typeNameList"
type="add" @cancel="modalPopCancel" @confirm="AddFormConfirm" v-if="modalPopData.type === 'encryption'" /> type="add" @cancel="modalPopCancel" @confirm="AddFormConfirm" v-if="modalPopData.type === 'encryption'" />
<AddKey :itemData="keyForm" :encryptionTypeDict="encryptionTypeList" :keyNameDict="keyNameList" type="add" <AddKey :itemData="keyForm" :encryptionTypeDict="encryptionTypeList" :keyNameDict="keyNameList" type="add"
@cancel="modalPopCancel" @confirm="AddFormConfirm" v-if="modalPopData.type === 'key'" /> @cancel="modalPopCancel" @confirm="AddFormConfirm" v-if="modalPopData.type === 'key'" />
<UploadModule @cancel="modalPopCancel" @confirm="AddFormConfirm" v-if="modalPopData.type === 'upload'" />
</template> </template>
</ModalPop> </ModalPop>
</div> </div>
...@@ -271,9 +299,9 @@ onMounted(() => { ...@@ -271,9 +299,9 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.app-container__body { .app-container__body {
height: calc( // height: calc(
100vh - var(--navbar-height) - var(--container-pd) - var(--container-pd) // 100vh - var(--navbar-height) - var(--container-pd) - var(--container-pd)
) !important; // ) !important;
.right { .right {
padding: 6px 10px 10px; padding: 6px 10px 10px;
height: 100%; height: 100%;
......
...@@ -96,6 +96,9 @@ watch( ...@@ -96,6 +96,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
form.value = {}
}
if (props.type === 'edit' && props.itemData) { if (props.type === 'edit' && props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
......
...@@ -134,6 +134,9 @@ watch( ...@@ -134,6 +134,9 @@ watch(
watch( watch(
() => props.itemData, () => props.itemData,
(newVal) => { (newVal) => {
if (props.type === "add") {
form.value = {}
}
if (props.itemData) { if (props.itemData) {
const data = JSON.parse(JSON.stringify(props.itemData)); const data = JSON.parse(JSON.stringify(props.itemData));
form.value = { ...form.value, ...data }; form.value = { ...form.value, ...data };
...@@ -170,7 +173,7 @@ watch( ...@@ -170,7 +173,7 @@ watch(
<el-form-item label="密钥值" prop="sectet_key_value" required> <el-form-item label="密钥值" prop="sectet_key_value" required>
<div style="width: 100%;display: flex;align-items: center;"> <div style="width: 100%;display: flex;align-items: center;">
<el-input v-model="form.sectet_key_value" disabled style="flex: 1;"></el-input> <el-input v-model="form.sectet_key_value" disabled style="flex: 1;"></el-input>
<el-button type="primary" style="margin-left: 8px;" @click="getKeyGeneration" v-if="!readOnly">密钥生成</el-button> <el-button type="primary" style="margin-left: 8px;" @click="getKeyGeneration" v-if="(!form.sectet_key_value) && !readOnly">密钥生成</el-button>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="测试"> <el-form-item label="测试">
......
<script setup lang="ts" name="AddClass">
import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import CustomUpload from "@/components/CustomUpload/index.vue";
import { importfile } from "@/api/ruleConfig/encryption";
const emit = defineEmits(["cancel", "confirm"]);
const uploadLoading = ref(false);
const uploadFile = ref({});
// 取消
const cancel = () => {
emit("cancel");
};
// 确认
const confirm = () => {
// emit("confirm");
if (!uploadFile.value.name) {
ElMessage.error("请先选择文件");
return;
}
uploadLoading.value = true;
const formData = new FormData();
formData.append('fileUpload', uploadFile.value.raw);
importfile(formData).then((res) => {
const { flag } = res;
if (flag) {
ElMessage.success('恢复成功');
emit("confirm");
}
uploadLoading.value = false;
}).catch(() => {
uploadLoading.value = false;
});
};
const handleFileChange = (file) => {
uploadFile.value = file;
};
</script>
<template>
<div>
<div class="mb20">
<CustomUpload :prompt='uploadFile ? uploadFile.name : ""' :loading="uploadLoading"
@file-change="handleFileChange" />
</div>
<div class="btn">
<el-button type="info" style="width: 150px;" @click="cancel">取消</el-button>
<el-button type="primary" :loading="uploadLoading" style="width: 150px;" @click="confirm">确认</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.btn {
padding: 20px;
display: flex;
justify-content: center;
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论