上传文件至「/」
This commit is contained in:
@@ -0,0 +1,808 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>服务方法管理 - DLL 扫描浏览</title>
|
||||
<!-- Vue 2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
|
||||
<!-- Element UI CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/theme-chalk/index.css">
|
||||
<!-- Element UI JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/index.js"></script>
|
||||
<!-- Axios -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js"></script>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #f0f2f5;
|
||||
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
color: #303133;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.top-bar {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.06);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
}
|
||||
.scan-form {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.scan-form .el-input {
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
}
|
||||
.scan-form .el-dropdown {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.el-dropdown-menu__item {
|
||||
max-width: 500px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.stats-bar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.04);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.stat-label {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.stat-value {
|
||||
color: #409eff;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
word-break: break-all;
|
||||
}
|
||||
.filter-bar {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.main-layout {
|
||||
width: 100%;
|
||||
}
|
||||
.left-panel {
|
||||
width: 100%;
|
||||
}
|
||||
.module-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.module-title i {
|
||||
color: #e6a23c;
|
||||
font-size: 18px;
|
||||
}
|
||||
.type-badge {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
font-family: Consolas, monospace;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
font-size: 13px;
|
||||
}
|
||||
.type-badge:hover {
|
||||
color: #66b1ff;
|
||||
background: #ecf5ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.type-badge.system {
|
||||
color: #606266;
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
.type-badge.system:hover {
|
||||
background: transparent;
|
||||
}
|
||||
.type-badge.active {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.param-desc {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
color: #909399;
|
||||
}
|
||||
.type-dialog .el-dialog__header {
|
||||
background: #409eff;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.type-dialog .el-dialog__title {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.type-dialog .el-dialog__headerbtn .el-dialog__close {
|
||||
color: #fff;
|
||||
}
|
||||
.type-dialog .el-dialog__body {
|
||||
padding: 20px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.dialog-namespace {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.dialog-type-desc {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.dialog-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.dialog-prop-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.dialog-prop-item {
|
||||
padding: 14px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.dialog-prop-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-bottom: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.dialog-prop-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.dialog-prop-type {
|
||||
color: #409eff;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
text-align: right;
|
||||
}
|
||||
.dialog-prop-type:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
.dialog-prop-type.system {
|
||||
color: #909399;
|
||||
cursor: default;
|
||||
}
|
||||
.dialog-prop-desc {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.dialog-empty {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #909399;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.type-dialog {
|
||||
width: 92% !important;
|
||||
}
|
||||
}
|
||||
.module-empty {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
color: #909399;
|
||||
}
|
||||
.type-table-card {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.04);
|
||||
}
|
||||
.modules-panel {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,.04);
|
||||
}
|
||||
.modules-panel .el-card__header {
|
||||
padding: 16px 20px;
|
||||
background: #fafbfc;
|
||||
}
|
||||
.module-collapse {
|
||||
border: none;
|
||||
}
|
||||
.module-collapse .el-collapse-item__header {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.module-collapse .el-collapse-item__content {
|
||||
padding: 0 0 16px 0;
|
||||
}
|
||||
.module-collapse-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.module-collapse-title span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.method-table {
|
||||
width: 100%;
|
||||
}
|
||||
.method-table .el-table__expanded-cell {
|
||||
padding: 16px 20px;
|
||||
background: #fafbfc;
|
||||
}
|
||||
.expand-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.expand-title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
.expand-params {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 8px 16px;
|
||||
align-items: baseline;
|
||||
}
|
||||
.expand-params .param-name {
|
||||
color: #303133;
|
||||
font-family: Consolas, monospace;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.expand-params .param-type-desc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.expand-params .param-desc {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.left-panel {
|
||||
min-width: 0;
|
||||
}
|
||||
.type-badge {
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" v-cloak>
|
||||
<div class="page-container">
|
||||
<div class="top-bar">
|
||||
<h1 class="page-title">服务方法管理 — DLL 扫描浏览</h1>
|
||||
<div class="scan-form">
|
||||
<el-input
|
||||
v-model="filePath"
|
||||
placeholder="请输入 DLL 文件绝对路径,例如 D:\\GTHINKING\\Bin\\S_XLXSBJA.dll"
|
||||
clearable
|
||||
@keyup.enter.native="scanDll">
|
||||
</el-input>
|
||||
<el-dropdown v-if="recentPaths.length" size="small" split-button type="info" @command="selectRecentPath" @click="filePath = recentPaths[0]">
|
||||
<i class="el-icon-time"></i> 最近路径
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="(p, idx) in recentPaths" :key="idx" :command="p" :title="p">{{ p }}</el-dropdown-item>
|
||||
<el-dropdown-item divided command="__clear__">清空记录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-button type="primary" icon="el-icon-search" :loading="scanning" @click="scanDll">
|
||||
扫描 DLL
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" :loading="loading" @click="loadData" :disabled="!macId">
|
||||
刷新数据
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="macId" class="stats-bar">
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">服务部件</span>
|
||||
<span class="stat-value" style="font-size: 16px;">{{ macId }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">模块数</span>
|
||||
<span class="stat-value">{{ srcmd.length }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">方法数</span>
|
||||
<span class="stat-value">{{ srcmethod.length }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">参数数</span>
|
||||
<span class="stat-value">{{ srcmethodparams.length }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">自定义类型</span>
|
||||
<span class="stat-value">{{ srctype.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="macId" class="filter-bar">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
placeholder="搜索方法名 / 参数名 / 类型名"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
style="width: 260px;">
|
||||
</el-input>
|
||||
<el-select
|
||||
v-if="viewMode === 'module'"
|
||||
v-model="jumpModule"
|
||||
placeholder="快速跳转模块"
|
||||
size="small"
|
||||
clearable
|
||||
style="width: 220px;"
|
||||
@change="jumpToModule">
|
||||
<el-option
|
||||
v-for="md in srcmd"
|
||||
:key="md.MDID"
|
||||
:label="md.MDNAME"
|
||||
:value="md.MDID">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<template v-if="viewMode === 'module'">
|
||||
<el-button size="small" icon="el-icon-arrow-down" @click="expandAllModules">全部展开</el-button>
|
||||
<el-button size="small" icon="el-icon-arrow-up" @click="collapseAllModules">全部收起</el-button>
|
||||
</template>
|
||||
<el-button size="small" icon="el-icon-close" @click="clearFilter">清空筛选</el-button>
|
||||
<el-radio-group v-model="viewMode" size="small">
|
||||
<el-radio-button label="module">按模块浏览</el-radio-button>
|
||||
<el-radio-button label="type">按类型浏览</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-tag v-if="viewMode === 'module' && filterText" type="info" size="small">{{ filteredMethodCount }} 个方法</el-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="macId" class="main-layout">
|
||||
<!-- 左侧:模块/方法列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- 按模块视图 -->
|
||||
<template v-if="viewMode === 'module'">
|
||||
<el-card class="modules-panel" shadow="never">
|
||||
<div slot="header" class="module-title">
|
||||
<i class="el-icon-s-operation" style="color: #409eff;"></i>
|
||||
<span>模块与方法</span>
|
||||
</div>
|
||||
<div v-if="filteredModules.length === 0" class="module-empty">没有匹配的数据</div>
|
||||
<el-collapse v-else v-model="expandedModules" class="module-collapse">
|
||||
<el-collapse-item v-for="md in filteredModules" :key="md.MDID" :name="md.MDID" :data-module-id="md.MDID">
|
||||
<template slot="title">
|
||||
<div class="module-collapse-title">
|
||||
<span :title="md.MDNAME">{{ md.MDNAME }}</span>
|
||||
<el-tag size="mini" type="info">{{ md.methods.length }} 个方法</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="md.methods.length === 0" class="module-empty">该模块下没有 AjaxMethod 方法</div>
|
||||
<el-table
|
||||
v-else
|
||||
:data="md.methods"
|
||||
class="method-table"
|
||||
size="small"
|
||||
stripe
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 600 }">
|
||||
<el-table-column type="expand" width="40">
|
||||
<template slot-scope="props">
|
||||
<div class="expand-header">
|
||||
<span class="expand-title">参数明细</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-document-copy" @click="copyMethodSignature(props.row)">复制签名</el-button>
|
||||
</div>
|
||||
<div class="expand-params">
|
||||
<template v-for="p in props.row.params">
|
||||
<span class="param-name">{{ p.PARAMNAME }}</span>
|
||||
<div class="param-type-desc">
|
||||
<span
|
||||
:class="['type-badge', { system: isSystemType(p.PARAMTYPE), active: isActiveType(p.PARAMTYPE) }]"
|
||||
@click.stop="showType(p.PARAMTYPE)">
|
||||
{{ p.PARAMTYPE }}
|
||||
</span>
|
||||
<span v-if="p.PARAMDESC" class="param-desc">{{ p.PARAMDESC }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="props.row.params.length === 0">
|
||||
<span class="param-name"></span>
|
||||
<span class="param-desc">无参数</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="METHODNAME" label="方法名" min-width="140"></el-table-column>
|
||||
<el-table-column prop="MODIFIERS" label="修饰符" min-width="100"></el-table-column>
|
||||
<el-table-column prop="AUTHREQUIRED" label="需登录" width="70" align="center"></el-table-column>
|
||||
<el-table-column prop="USEHTTPGET" label="支持GET" width="80" align="center"></el-table-column>
|
||||
<el-table-column label="返回类型" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<span
|
||||
:class="['type-badge', { system: isSystemType(scope.row.RETTYPE), active: isActiveType(scope.row.RETTYPE) }]"
|
||||
@click.stop="showType(scope.row.RETTYPE)">
|
||||
{{ scope.row.RETTYPE || 'void' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="RETDESC" label="返回值描述" min-width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="METHODDESC" label="方法描述" min-width="200" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<!-- 按类型视图 -->
|
||||
<template v-if="viewMode === 'type'">
|
||||
<el-card class="type-table-card" shadow="never">
|
||||
<div slot="header" class="module-title">
|
||||
<i class="el-icon-coin" style="color: #409eff;"></i>
|
||||
<span>自定义类型一览</span>
|
||||
</div>
|
||||
<el-table :data="filteredTypes" style="width: 100%" size="small" stripe>
|
||||
<el-table-column label="类型全称" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<el-link type="primary" @click="showType(scope.row.TYPEFULLNAME)">{{ scope.row.TYPEFULLNAME }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="NAMESPACE" label="命名空间" min-width="180"></el-table-column>
|
||||
<el-table-column prop="TYPEDESC" label="描述" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 类型定义弹窗 -->
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
:title="currentTypeFullName || '类型定义'"
|
||||
width="70%"
|
||||
:close-on-click-modal="true"
|
||||
custom-class="type-dialog">
|
||||
<div v-if="currentTypeNamespace" class="dialog-namespace">{{ currentTypeNamespace }}</div>
|
||||
<div v-if="currentTypeDesc" class="dialog-type-desc">{{ currentTypeDesc }}</div>
|
||||
<div class="dialog-section-title">公共属性 / 字段({{ currentTypeProps.length }})</div>
|
||||
<div v-if="currentTypeProps.length === 0" class="dialog-empty">该类型没有公共属性/字段</div>
|
||||
<div v-else class="dialog-prop-list">
|
||||
<div v-for="prop in currentTypeProps" :key="prop.PROPNAME + '_' + prop.PROPTYPEFULLNAME" class="dialog-prop-item">
|
||||
<div class="dialog-prop-header">
|
||||
<span class="dialog-prop-name">{{ prop.PROPNAME }}</span>
|
||||
<span
|
||||
:class="['dialog-prop-type', { system: isSystemType(prop.PROPTYPEFULLNAME) }]"
|
||||
@click="showType(prop.PROPTYPEFULLNAME)">
|
||||
{{ prop.PROPTYPE || prop.PROPTYPEFULLNAME }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="prop.PROPDESC" class="dialog-prop-desc">{{ prop.PROPDESC }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<div v-if="!macId && !scanning && !loading" class="empty-tip">
|
||||
<i class="el-icon-upload" style="font-size: 56px; color: #dcdfe6; display: block; margin-bottom: 16px;"></i>
|
||||
<div>输入 DLL 文件路径并点击“扫描 DLL”开始解析服务方法</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
// 服务调用基地址,按项目实际部署调整
|
||||
baseUrl: 'http://127.0.0.1/GTHINKING/AjaxService/S_XLXSBJ/102373019.ashx',
|
||||
filePath: '',
|
||||
scanning: false,
|
||||
loading: false,
|
||||
macId: '',
|
||||
srcmd: [],
|
||||
srcmethod: [],
|
||||
srcmethodparams: [],
|
||||
srctype: [],
|
||||
srcprop: [],
|
||||
filterText: '',
|
||||
viewMode: 'module',
|
||||
expandedModules: [],
|
||||
currentTypeFullName: '',
|
||||
dialogVisible: false,
|
||||
recentPaths: [],
|
||||
jumpModule: '',
|
||||
},
|
||||
computed: {
|
||||
currentTypeNamespace() {
|
||||
const t = this.srctype.find(x => x.TYPEFULLNAME === this.currentTypeFullName);
|
||||
return t && t.NAMESPACE ? t.NAMESPACE : '';
|
||||
},
|
||||
currentTypeDesc() {
|
||||
const t = this.srctype.find(x => x.TYPEFULLNAME === this.currentTypeFullName);
|
||||
return t && t.TYPEDESC ? t.TYPEDESC : '';
|
||||
},
|
||||
currentTypeProps() {
|
||||
return this.srcprop.filter(x => x.TYPEFULLNAME === this.currentTypeFullName);
|
||||
},
|
||||
filteredModules() {
|
||||
const text = (this.filterText || '').toLowerCase();
|
||||
return this.srcmd.map(md => {
|
||||
const methods = this.srcmethod
|
||||
.filter(m => m.MACID === md.MACID && m.MDID === md.MDID)
|
||||
.map(m => {
|
||||
const params = this.srcmethodparams.filter(p =>
|
||||
p.MACID === m.MACID && p.MDID === m.MDID && p.METHODNAME === m.METHODNAME
|
||||
);
|
||||
return { ...m, params };
|
||||
});
|
||||
return { ...md, methods };
|
||||
}).filter(md => {
|
||||
if (!text) return true;
|
||||
return md.methods.some(m =>
|
||||
(m.METHODNAME || '').toLowerCase().includes(text) ||
|
||||
m.params.some(p =>
|
||||
(p.PARAMNAME || '').toLowerCase().includes(text) ||
|
||||
(p.PARAMTYPE || '').toLowerCase().includes(text)
|
||||
) ||
|
||||
(m.RETTYPE || '').toLowerCase().includes(text)
|
||||
);
|
||||
});
|
||||
},
|
||||
filteredTypes() {
|
||||
const text = (this.filterText || '').toLowerCase();
|
||||
if (!text) return this.srctype;
|
||||
return this.srctype.filter(t =>
|
||||
(t.TYPEFULLNAME || '').toLowerCase().includes(text) ||
|
||||
(t.TYPENAME || '').toLowerCase().includes(text) ||
|
||||
(t.NAMESPACE || '').toLowerCase().includes(text)
|
||||
);
|
||||
},
|
||||
filteredMethodCount() {
|
||||
return this.filteredModules.reduce((sum, md) => sum + md.methods.length, 0);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isSystemType(typeName) {
|
||||
if (!typeName) return true;
|
||||
const lower = typeName.toLowerCase();
|
||||
if (lower.startsWith('system.')) return true;
|
||||
if (lower.startsWith('microsoft.')) return true;
|
||||
if (['string', 'int', 'bool', 'long', 'double', 'float', 'decimal', 'datetime', 'object', 'void', 'byte', 'char', 'short', 'uint', 'ulong', 'ushort'].includes(lower)) return true;
|
||||
if (lower.endsWith('[]')) return this.isSystemType(lower.replace('[]', ''));
|
||||
if (lower.endsWith('?')) return this.isSystemType(lower.replace('?', ''));
|
||||
if (lower.includes('<')) {
|
||||
const base = lower.split('<')[0];
|
||||
const generic = ['list', 'ienumerable', 'icollection', 'dictionary', 'nullable'];
|
||||
if (generic.some(g => base.includes(g))) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isActiveType(typeName) {
|
||||
if (!typeName || this.isSystemType(typeName)) return false;
|
||||
let base = typeName;
|
||||
if (base.endsWith('[]')) base = base.replace('[]', '');
|
||||
if (base.endsWith('?')) base = base.replace('?', '');
|
||||
if (base.indexOf('<') > 0) base = base.substring(0, base.indexOf('<'));
|
||||
return base.trim() === this.currentTypeFullName;
|
||||
},
|
||||
callMethod(methodName, params) {
|
||||
const url = this.baseUrl + '/' + methodName;
|
||||
return axios.post(url, params, {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}).then(res => {
|
||||
const data = res.data;
|
||||
if (data && data.success === false) {
|
||||
throw new Error(data.msg || '服务调用失败');
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
scanDll() {
|
||||
if (!this.filePath) {
|
||||
this.$message.warning('请输入 DLL 文件路径');
|
||||
return;
|
||||
}
|
||||
this.scanning = true;
|
||||
this.callMethod('SaveServiceMethodInfo', { filePath: this.filePath })
|
||||
.then(data => {
|
||||
this.$message.success(data.msg || '扫描完成');
|
||||
this.macId = data.macId || '';
|
||||
this.currentTypeFullName = '';
|
||||
this.saveRecentPath(this.filePath);
|
||||
return this.loadData();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err.message || '扫描失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.scanning = false;
|
||||
});
|
||||
},
|
||||
loadData() {
|
||||
if (!this.macId) {
|
||||
this.$message.warning('请先扫描 DLL');
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.loading = true;
|
||||
return this.callMethod('GetServiceMethodData', { macId: this.macId })
|
||||
.then(data => {
|
||||
this.srcmd = data.SRCMD || [];
|
||||
this.srcmethod = data.SRCMETHOD || [];
|
||||
this.srcmethodparams = data.SRCMETHODPARAMS || [];
|
||||
this.srctype = data.SRCTYPE || [];
|
||||
this.srcprop = data.SRCPROP || [];
|
||||
this.expandedModules = (data.SRCMD || []).map(md => md.MDID);
|
||||
this.currentTypeFullName = '';
|
||||
this.$message.success('数据加载完成');
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err.message || '加载数据失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
showType(typeName) {
|
||||
if (!typeName) return;
|
||||
if (this.isSystemType(typeName)) return;
|
||||
let base = typeName;
|
||||
if (base.endsWith('[]')) base = base.replace('[]', '');
|
||||
if (base.endsWith('?')) base = base.replace('?', '');
|
||||
if (base.indexOf('<') > 0) base = base.substring(0, base.indexOf('<'));
|
||||
base = base.trim();
|
||||
if (!this.srctype.some(t => t.TYPEFULLNAME === base)) {
|
||||
this.$message.info('该类型未在扫描结果中找到,可能为系统类型或未被引用');
|
||||
return;
|
||||
}
|
||||
this.currentTypeFullName = base;
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
loadRecentPaths() {
|
||||
try {
|
||||
const list = JSON.parse(localStorage.getItem('svcMethodRecentPaths') || '[]');
|
||||
this.recentPaths = Array.isArray(list) ? list : [];
|
||||
} catch (e) {
|
||||
this.recentPaths = [];
|
||||
}
|
||||
},
|
||||
saveRecentPath(path) {
|
||||
if (!path) return;
|
||||
let list = this.recentPaths.filter(p => p !== path);
|
||||
list.unshift(path);
|
||||
if (list.length > 10) list = list.slice(0, 10);
|
||||
this.recentPaths = list;
|
||||
try {
|
||||
localStorage.setItem('svcMethodRecentPaths', JSON.stringify(list));
|
||||
} catch (e) {}
|
||||
},
|
||||
selectRecentPath(path) {
|
||||
if (path === '__clear__') {
|
||||
this.clearRecentPaths();
|
||||
this.$message.success('已清空最近路径');
|
||||
} else {
|
||||
this.filePath = path;
|
||||
}
|
||||
},
|
||||
clearRecentPaths() {
|
||||
this.recentPaths = [];
|
||||
try {
|
||||
localStorage.removeItem('svcMethodRecentPaths');
|
||||
} catch (e) {}
|
||||
},
|
||||
expandAllModules() {
|
||||
this.expandedModules = this.srcmd.map(md => md.MDID);
|
||||
},
|
||||
collapseAllModules() {
|
||||
this.expandedModules = [];
|
||||
},
|
||||
jumpToModule() {
|
||||
if (!this.jumpModule) return;
|
||||
const mdId = this.jumpModule;
|
||||
if (!this.expandedModules.includes(mdId)) {
|
||||
this.expandedModules.push(mdId);
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const el = document.querySelector('[data-module-id="' + mdId + '"]');
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
},
|
||||
clearFilter() {
|
||||
this.filterText = '';
|
||||
this.jumpModule = '';
|
||||
},
|
||||
copyMethodSignature(method) {
|
||||
const params = method.params.map(p => p.PARAMTYPE + ' ' + p.PARAMNAME).join(', ');
|
||||
const sig = (method.MODIFIERS || 'public') + ' ' + (method.RETTYPE || 'void') + ' ' + method.METHODNAME + '(' + params + ')';
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(sig).then(() => {
|
||||
this.$message.success('方法签名已复制');
|
||||
}).catch(() => {
|
||||
this.$message.warning('复制失败,请手动复制');
|
||||
});
|
||||
} else {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = sig;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
this.$message.success('方法签名已复制');
|
||||
} catch (e) {
|
||||
this.$message.warning('复制失败,请手动复制');
|
||||
}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRecentPaths();
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const path = urlParams.get('path');
|
||||
if (path) {
|
||||
this.filePath = path;
|
||||
} else if (this.recentPaths.length) {
|
||||
this.filePath = this.recentPaths[0];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user