123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 |
- <template>
- <div class="page">
- <div class="plugin-container">
- <el-card shadow="never">
- <div class="plugin-header">
- <h3>API插件管理</h3>
- </div>
- <el-form :model="params" inline ref="queryRef">
- <el-form-item label="关键字" prop="keyWord">
- <el-input v-model="params.keyWord" placeholder="输入插件名称或描述" clearable style="width: 170px" @keyup.enter.native="getList(1)" />
- </el-form-item>
- <el-form-item label="类型" prop="type">
- <el-select v-model="params.type" placeholder="请选择类型" clearable style="width: 140px">
- <el-option label="全部" value="" />
- <!-- <el-option label="Go" value="Go" /> -->
- <el-option label="JavaScript" value="JavaScript" />
- <el-option label="LUA" value="LUA" />
- </el-select>
- </el-form-item>
- <el-form-item label="分类" prop="category">
- <el-select v-model="params.category" placeholder="请选择分类" clearable style="width: 140px">
- <el-option label="全部" value="" />
- <el-option label="前置插件" value="Before" />
- <el-option label="后置插件" value="After" />
- </el-select>
- </el-form-item>
- <!-- <el-form-item label="日期范围" prop="dateRange">
- <el-date-picker v-model="params.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" />
- </el-form-item> -->
- <el-form-item>
- <el-button type="primary" class="ml10" @click="getList(1)">
- <el-icon>
- <ele-Search />
- </el-icon>
- 查询
- </el-button>
- <el-button type="primary" @click="addOrEdit()" v-auth="'add'">
- <el-icon>
- <ele-Plus />
- </el-icon>
- 新增插件
- </el-button>
- </el-form-item>
- </el-form>
- <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
- <el-table-column type="selection" width="40" align="center" />
- <el-table-column prop="name" label="插件名称" v-col="'name'" min-width="140" show-overflow-tooltip></el-table-column>
- <el-table-column prop="type" label="类型" v-col="'type'" width="120" align="center">
- <template #default="scope">
- <el-tag size="small" type="success" v-if="scope.row.type === 'Go'">Go</el-tag>
- <el-tag size="small" v-else-if="scope.row.type === 'JavaScript'">JavaScript</el-tag>
- <el-tag size="small" type="warning" v-else-if="scope.row.type === 'LUA'">LUA</el-tag>
- <span v-else>{{ scope.row.type }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="category" label="分类" v-col="'category'" width="120" align="center">
- <template #default="scope">
- <el-tag size="small" type="info" v-if="scope.row.category === 'Before'">前置插件</el-tag>
- <el-tag size="small" type="info" v-else-if="scope.row.category === 'After'">后置插件</el-tag>
- <span v-else>{{ scope.row.category }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="description" label="描述" v-col="'description'" show-overflow-tooltip></el-table-column>
- <!-- <el-table-column prop="createdAt" label="创建时间" width="160" align="center"></el-table-column> -->
- <el-table-column label="操作" width="250" align="center" fixed="right" v-col="'handle'">
- <template #default="scope">
- <div class="flex-row">
- <el-button size="small" text type="primary" @click="viewDetail(scope.row)" v-auth="'view'">查看</el-button>
- <el-button size="small" text type="warning" @click="addOrEdit(scope.row)" v-auth="'edit'">编辑</el-button>
- <el-button size="small" text type="success" @click="testPlugin(scope.row)" v-auth="'test'">测试</el-button>
- <el-button size="small" text type="danger" @click="deletePlugin(scope.row)" v-auth="'del'">删除</el-button>
- </div>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
- </el-card>
- </div>
- <!-- 插件表单弹窗 -->
- <el-dialog v-model="dialogVisible" :title="formTitle" width="800px" destroy-on-close>
- <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
- <el-form-item label="插件名称" prop="name">
- <el-input v-model="form.name" placeholder="请输入插件名称" />
- </el-form-item>
- <el-form-item label="插件类型" prop="type">
- <el-select v-model="form.type" placeholder="请选择插件类型" style="width: 100%">
- <el-option label="Go" value="Go" />
- <el-option label="JavaScript" value="JavaScript" />
- <el-option label="LUA" value="LUA" />
- </el-select>
- </el-form-item>
- <el-form-item label="插件分类" prop="category">
- <el-select v-model="form.category" placeholder="请选择插件分类" style="width: 100%">
- <el-option label="前置插件" value="Before" />
- <el-option label="后置插件" value="After" />
- </el-select>
- </el-form-item>
- <el-form-item label="插件内容" prop="content">
- <plugin-editor v-model="form.content" :height="300" :language="form.type" />
- <div class="form-tip">
- <template v-if="form.type === 'Go'">插件内容为Go插件的路径</template>
- <template v-else>插件内容为脚本代码</template>
- </div>
- </el-form-item>
- <el-form-item label="描述" prop="description">
- <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入描述信息" />
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
- </span>
- </template>
- </el-dialog>
- <!-- 插件详情弹窗 -->
- <el-dialog v-model="detailVisible" title="插件详情" width="800px" destroy-on-close>
- <el-descriptions :column="1" border>
- <el-descriptions-item label="插件名称">{{ detail.name }}</el-descriptions-item>
- <el-descriptions-item label="插件类型">
- <el-tag size="small" type="success" v-if="detail.type === 'Go'">Go</el-tag>
- <el-tag size="small" v-else-if="detail.type === 'JavaScript'">JavaScript</el-tag>
- <el-tag size="small" type="warning" v-else-if="detail.type === 'LUA'">LUA</el-tag>
- <span v-else>{{ detail.type }}</span>
- </el-descriptions-item>
- <el-descriptions-item label="插件分类">
- <el-tag size="small" type="info" v-if="detail.category === 'Before'">前置插件</el-tag>
- <el-tag size="small" type="info" v-else-if="detail.category === 'After'">后置插件</el-tag>
- <span v-else>{{ detail.category }}</span>
- </el-descriptions-item>
- <el-descriptions-item label="插件内容">
- <div class="code-preview">
- <plugin-editor v-model="detail.content" :height="200" :language="detail.type" :readonly="true" />
- </div>
- </el-descriptions-item>
- <el-descriptions-item label="描述">{{ detail.description || '-' }}</el-descriptions-item>
- <el-descriptions-item label="创建时间">{{ detail.createdAt }}</el-descriptions-item>
- <el-descriptions-item label="更新时间">{{ detail.updatedAt }}</el-descriptions-item>
- </el-descriptions>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="detailVisible = false">关闭</el-button>
- </span>
- </template>
- </el-dialog>
- <!-- 插件测试弹窗 -->
- <el-dialog v-model="testVisible" :title="`插件测试 - ${currentTestPluginName}`" width="900px" destroy-on-close>
- <div class="test-container">
- <el-form :model="testForm" ref="testFormRef" label-width="100px">
- <el-form-item label="上下文数据" prop="context">
- <div class="context-toolbar">
- <el-button-group>
- <el-tooltip content="格式化JSON" placement="top">
- <el-button type="primary" :icon="Document" @click="formatJson" plain size="small">格式化</el-button>
- </el-tooltip>
- <el-tooltip content="模板帮助你快速创建测试数据" placement="top">
- <el-dropdown @command="useTestTemplate" trigger="click">
- <el-button type="primary" plain size="small">
- 使用模板 <el-icon class="el-icon--right"><arrow-down /></el-icon>
- </el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item v-for="(temp, index) in testTemplates" :key="index" :command="temp.value">
- {{ temp.name }}
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </el-tooltip>
- </el-button-group>
- </div>
- <el-input
- v-model="testForm.context"
- type="textarea"
- :rows="8"
- placeholder="请输入测试用的上下文数据,JSON格式。例如:{ "key": "value" }"
- class="json-textarea"
- :class="{ 'textarea-error': testResult.error }"
- />
- <div v-if="testResult.error" class="error-message">
- <el-icon><warning /></el-icon> {{ testResult.error }}
- </div>
- </el-form-item>
- </el-form>
- <div class="test-result" v-if="testResult.message">
- <div class="result-header">
- <div class="result-title">测试结果</div>
- <el-tag :type="testResult.success ? 'success' : 'danger'" effect="dark">
- {{ testResult.success ? '测试成功' : '测试失败' }}
- </el-tag>
- </div>
-
- <div class="result-message">{{ testResult.message }}</div>
-
- <div class="result-data" v-if="testResult.data">
- <div class="result-data-header">
- <div class="result-label">返回数据</div>
- <el-button type="primary" link size="small" @click="copyToClipboard(testResult.data)">
- <el-icon><copy-document /></el-icon> 复制
- </el-button>
- </div>
- <pre class="data-preview">{{ testResult.data }}</pre>
- </div>
- </div>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="testVisible = false">关闭</el-button>
- <el-button type="primary" @click="runTest" :loading="testLoading">
- <el-icon v-if="!testLoading"><video-play /></el-icon>
- <span>执行测试</span>
- </el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref, reactive, onMounted } from "vue";
- import { ElMessageBox, ElMessage } from "element-plus";
- import api from "/@/api/modules/apiHub";
- import PluginEditor from "./component/PluginEditor.vue";
- import { ArrowDown, Document, Warning, VideoPlay, CopyDocument } from '@element-plus/icons-vue';
- // 定义插件接口类型
- interface PluginInfo {
- id?: number;
- name: string;
- type: string;
- category: string;
- content: string;
- description?: string;
- createdAt?: string;
- updatedAt?: string;
- }
- // 引用组件
- const queryRef = ref();
- const formRef = ref();
- const testFormRef = ref();
- // 查询参数
- const params = reactive({
- keyWord: '',
- type: '',
- category: '',
- dateRange: [],
- pageNum: 1,
- pageSize: 10,
- total: 0
- });
- // 表格数据
- const tableData = ref<PluginInfo[]>([]);
- const loading = ref(false);
- // 获取插件列表
- const getList = async (pageNum?: number) => {
- if (pageNum) {
- params.pageNum = pageNum;
- }
- loading.value = true;
- try {
- const res = await api.plugin.list(params);
- // 处理API响应数据
-
- // 尝试所有可能的数据结构
- tableData.value = []; // 清空当前数据
-
- if (res) {
- // 1. 检查 res.data
- if (res.data) {
- // 1.1 直接是数组
- if (Array.isArray(res.data)) {
- tableData.value = res.data;
- params.total = res.total || res.data.length || 0;
- }
- // 1.2 有data属性
- else if (res.data.data && Array.isArray(res.data.data)) {
- tableData.value = res.data.data;
- params.total = res.data.total || res.data.data.length || 0;
- }
- // 1.3 有Data属性(首字母大写)
- else if (res.data.Data && Array.isArray(res.data.Data)) {
- tableData.value = res.data.Data;
- params.total = res.data.Total || res.data.Data.length || 0;
- }
- }
-
- // 2. 检查 res.Data(可能首字母大写)
- else if (res.Data) {
- if (Array.isArray(res.Data)) {
- tableData.value = res.Data;
- params.total = res.Total || res.Data.length || 0;
- }
- }
- }
-
- // 检查表格数据中的属性名称是否首字母大写,如果是,则转换为小写
- // 这是因为表格组件的prop属性期望的是小写属性名
- if (tableData.value.length > 0) {
- tableData.value = tableData.value.map(item => {
- // 如果有首字母大写的属性,增加小写形式的副本
- const newItem = {...item};
- if (newItem.Id !== undefined) newItem.id = newItem.Id;
- if (newItem.Name !== undefined) newItem.name = newItem.Name;
- if (newItem.Type !== undefined) newItem.type = newItem.Type;
- if (newItem.Category !== undefined) newItem.category = newItem.Category;
- if (newItem.Content !== undefined) newItem.content = newItem.Content;
- if (newItem.Description !== undefined) newItem.description = newItem.Description;
- if (newItem.CreatedAt !== undefined) newItem.createdAt = newItem.CreatedAt;
- if (newItem.UpdatedAt !== undefined) newItem.updatedAt = newItem.UpdatedAt;
- return newItem;
- });
- }
- } catch (error) {
- // 获取插件列表失败
- tableData.value = [];
- params.total = 0;
- } finally {
- loading.value = false;
- }
- };
- // 表单相关
- const dialogVisible = ref(false);
- const formTitle = ref('');
- const submitLoading = ref(false);
- const form = reactive<PluginInfo>({
- name: '',
- type: 'JavaScript',
- category: 'Before',
- content: '',
- description: ''
- });
- // 表单验证规则
- const rules = {
- name: [{ required: true, message: '请输入插件名称', trigger: 'blur' }],
- type: [{ required: true, message: '请选择插件类型', trigger: 'change' }],
- category: [{ required: true, message: '请选择插件分类', trigger: 'change' }],
- content: [{ required: true, message: '请输入插件内容', trigger: 'blur' }]
- };
- // 重置查询表单
- const resetQuery = () => {
- if (queryRef.value) {
- queryRef.value.resetFields();
- }
- params.keyWord = '';
- params.type = '';
- params.category = '';
- params.dateRange = [];
- getList(1);
- };
- // 新增或编辑插件
- const addOrEdit = (row?: PluginInfo) => {
- resetForm();
- if (row && row.id) {
- formTitle.value = '编辑插件';
- // 获取详情
- api.plugin.get(row.id).then(res => {
- // 处理API返回数据
- let pluginData = null;
-
- // 检查各种可能的数据结构
- if (res.data) {
- pluginData = res.data;
- } else if (res.Data) {
- pluginData = res.Data;
- } else if (res) {
- pluginData = res;
- }
-
- // 首字母大写转小写处理
- if (pluginData) {
- // 如果有首字母大写的属性,添加小写形式
- if (pluginData.Id !== undefined) pluginData.id = pluginData.Id;
- if (pluginData.Name !== undefined) pluginData.name = pluginData.Name;
- if (pluginData.Type !== undefined) pluginData.type = pluginData.Type;
- if (pluginData.Category !== undefined) pluginData.category = pluginData.Category;
- if (pluginData.Content !== undefined) pluginData.content = pluginData.Content;
- if (pluginData.Description !== undefined) pluginData.description = pluginData.Description;
- }
-
- // 将处理后的数据赋值给表单
- Object.assign(form, pluginData);
- });
- } else {
- formTitle.value = '新增插件';
- }
- dialogVisible.value = true;
- };
- // 重置表单
- const resetForm = () => {
- if (formRef.value) {
- formRef.value.resetFields();
- }
- form.id = undefined;
- form.name = '';
- form.type = 'JavaScript';
- form.category = 'Before';
- form.content = '';
- form.description = '';
- };
- // 提交表单
- const submitForm = () => {
- if (!formRef.value) return;
-
- formRef.value.validate(async (valid: boolean) => {
- if (!valid) return;
-
- submitLoading.value = true;
- try {
- if (form.id) {
- // 编辑
- await api.plugin.edit(form);
- ElMessage.success('编辑成功');
- } else {
- // 新增
- await api.plugin.add(form);
- ElMessage.success('新增成功');
- }
- dialogVisible.value = false;
- getList();
- } catch (error) {
- // 保存插件失败
- } finally {
- submitLoading.value = false;
- }
- });
- };
- // 插件详情
- const detailVisible = ref(false);
- const detail = reactive<PluginInfo>({
- name: '',
- type: 'JavaScript',
- category: 'Before',
- content: '',
- description: ''
- });
- // 查看插件详情
- const viewDetail = (row: PluginInfo) => {
- api.plugin.get(row.id).then(res => {
- // 处理API返回数据
- let pluginData = null;
-
- // 检查各种可能的数据结构
- if (res.data) {
- pluginData = res.data;
- } else if (res.Data) {
- pluginData = res.Data;
- } else if (res) {
- pluginData = res;
- }
-
- // 首字母大写转小写处理
- if (pluginData) {
- // 如果有首字母大写的属性,添加小写形式
- if (pluginData.Id !== undefined) pluginData.id = pluginData.Id;
- if (pluginData.Name !== undefined) pluginData.name = pluginData.Name;
- if (pluginData.Type !== undefined) pluginData.type = pluginData.Type;
- if (pluginData.Category !== undefined) pluginData.category = pluginData.Category;
- if (pluginData.Content !== undefined) pluginData.content = pluginData.Content;
- if (pluginData.Description !== undefined) pluginData.description = pluginData.Description;
- }
-
- // 将处理后的数据赋值给详情对象
- Object.assign(detail, pluginData);
- detailVisible.value = true;
- });
- };
- // 删除插件
- const deletePlugin = (row: PluginInfo) => {
- ElMessageBox.confirm(`确定要删除插件 "${row.name}" 吗?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- try {
- await api.plugin.delete([row.id]);
- ElMessage.success('删除成功');
- getList();
- } catch (error) {
- // 删除插件失败
- }
- }).catch(() => {});
- };
- // 插件测试相关
- const testVisible = ref(false);
- const testLoading = ref(false);
- const currentTestPluginId = ref<number>(0);
- const currentTestPluginName = ref('');
- const testForm = reactive({
- context: '{}'
- });
- const testResult = reactive({
- success: false,
- message: '',
- data: null,
- error: ''
- });
- // 测试模板
- const testTemplates = [
- { name: '空对象', value: '{}' },
- { name: '简单示例', value: '{\n "key": "value",\n "number": 123,\n "boolean": true\n}' },
- { name: '请求参数示例', value: '{\n "headers": {\n "content-type": "application/json"\n },\n "body": {\n "data": "example"\n },\n "query": {\n "id": 1\n }\n}' }
- ];
- // 打开测试窗口
- const testPlugin = (row: PluginInfo) => {
- currentTestPluginId.value = row.id;
- currentTestPluginName.value = row.name || `插件ID: ${row.id}`;
- testForm.context = '{}';
- testResult.success = false;
- testResult.message = '';
- testResult.data = null;
- testResult.error = '';
- testVisible.value = true;
- };
- // 使用测试模板
- const useTestTemplate = (template: string) => {
- testForm.context = template;
- };
- // 格式化JSON
- const formatJson = () => {
- try {
- const obj = JSON.parse(testForm.context);
- testForm.context = JSON.stringify(obj, null, 2);
- testResult.error = '';
- } catch (e) {
- testResult.error = `JSON格式错误: ${e instanceof Error ? e.message : String(e)}`;
- }
- };
- // 执行测试
- const runTest = async () => {
- if (!currentTestPluginId.value) return;
-
- // 先格式化JSON
- try {
- const parsedJson = JSON.parse(testForm.context);
- testForm.context = JSON.stringify(parsedJson, null, 2);
- testResult.error = '';
- } catch (e) {
- testResult.error = `JSON格式错误: ${e instanceof Error ? e.message : String(e)}`;
- ElMessage.error(`JSON格式错误: ${e instanceof Error ? e.message : String(e)}`);
- return;
- }
-
- testLoading.value = true;
- testResult.success = false;
- testResult.message = '';
- testResult.data = null;
-
- try {
- // 发送测试请求
- const contextData = JSON.parse(testForm.context);
- const res = await api.plugin.test({
- id: currentTestPluginId.value,
- context: contextData
- });
-
- // 处理不同格式的响应
- if (res.data) {
- if (typeof res.data.success === 'boolean') {
- // 标准格式的响应
- testResult.success = res.data.success;
- testResult.message = res.data.message || (
- testResult.success ? '测试成功' : '测试失败'
- );
- testResult.data = res.data.data;
- } else {
- // 直接返回的数据
- testResult.success = true;
- testResult.message = '测试成功';
- testResult.data = res.data;
- }
- } else if (res.success !== undefined) {
- // 有success字段在根级别的响应
- testResult.success = res.success;
- testResult.message = res.message || (
- testResult.success ? '测试成功' : '测试失败'
- );
- testResult.data = res.data;
- } else {
- // 其他格式
- testResult.success = true;
- testResult.message = '测试成功';
- testResult.data = res;
- }
-
- // 格式化响应数据显示
- if (testResult.data && typeof testResult.data === 'object') {
- testResult.data = JSON.stringify(testResult.data, null, 2);
- }
- } catch (error) {
- // 测试插件失败
- testResult.success = false;
- testResult.message = '测试发生错误';
- testResult.data = error instanceof Error ? error.message : String(error);
- } finally {
- testLoading.value = false;
- }
- };
- // 复制到剪切板
- const copyToClipboard = (text: string) => {
- try {
- navigator.clipboard.writeText(text).then(() => {
- ElMessage.success('已复制到剪切板');
- }).catch(() => {
- ElMessage.error('复制失败,请手动复制');
- });
- } catch (error) {
- // 兼容不支持 Clipboard API 的浏览器
- const textarea = document.createElement('textarea');
- textarea.textContent = text;
- textarea.style.position = 'fixed';
- document.body.appendChild(textarea);
- textarea.select();
-
- try {
- document.execCommand('copy');
- ElMessage.success('已复制到剪切板');
- } catch (err) {
- ElMessage.error('复制失败,请手动复制');
- } finally {
- document.body.removeChild(textarea);
- }
- }
- };
- // 组件挂载后获取数据
- onMounted(() => {
- getList();
- });
- </script>
- <style scoped>
- .plugin-container {
- width: 100%;
- }
- .plugin-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- }
- .plugin-header h3 {
- margin: 0;
- font-size: 18px;
- font-weight: 500;
- }
- .flex-row {
- display: flex;
- flex-wrap: nowrap;
- justify-content: center;
- }
- .code-preview {
- width: 100%;
- background-color: #f5f7fa;
- border-radius: 4px;
- }
- /* 测试界面样式 */
- .test-container {
- padding: 10px;
- }
- .context-toolbar {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- margin-bottom: 10px;
- }
- .json-textarea {
- margin-bottom: 5px;
- font-family: monospace;
- }
- .textarea-error {
- border-color: #f56c6c;
- }
- .error-message {
- color: #f56c6c;
- font-size: 12px;
- display: flex;
- align-items: center;
- margin-top: 5px;
- }
- .error-message .el-icon {
- margin-right: 5px;
- }
- .test-result {
- margin-top: 20px;
- padding: 15px;
- background-color: #f8f8f8;
- border-radius: 4px;
- border: 1px solid #e6e6e6;
- }
- .result-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- }
- .result-title {
- font-weight: bold;
- font-size: 16px;
- }
- .result-message {
- margin: 10px 0;
- word-break: break-word;
- }
- .result-data {
- margin-top: 15px;
- }
- .result-data-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
- .result-label {
- font-weight: bold;
- color: #606266;
- }
- .data-preview {
- background-color: #f5f7fa;
- padding: 10px;
- border-radius: 4px;
- margin: 0;
- overflow: auto;
- max-height: 300px;
- font-family: monospace;
- white-space: pre-wrap;
- word-break: break-word;
- }
- .form-tip {
- font-size: 12px;
- color: #909399;
- margin-top: 4px;
- }
- .test-container {
- width: 100%;
- }
- .test-result {
- margin-top: 20px;
- padding: 16px;
- background-color: #f5f7fa;
- border-radius: 4px;
- }
- .result-title {
- font-weight: bold;
- margin-bottom: 10px;
- }
- .result-message {
- margin-top: 10px;
- padding: 8px;
- background-color: #fff;
- border-radius: 4px;
- }
- .result-data {
- margin-top: 10px;
- }
- .result-label {
- font-weight: bold;
- margin-bottom: 5px;
- }
- .result-data pre {
- background-color: #fff;
- padding: 8px;
- border-radius: 4px;
- white-space: pre-wrap;
- word-break: break-all;
- max-height: 200px;
- overflow-y: auto;
- }
- </style>
|