kylx365_db_admin.html 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  6. <title>数据库管理</title>
  7. <script src="https://kylx365-1253256735.file.myqcloud.com/js/jquery-1.10.2.min.js"></script>
  8. <script src="https://kylx365-1253256735.file.myqcloud.com/js/vue.min.js"></script>
  9. <style>
  10. /* 表格项样式 */
  11. .table-item {
  12. padding: 10px;
  13. margin: 5px 0;
  14. background: white;
  15. border-radius: 4px;
  16. cursor: pointer;
  17. transition: background-color 0.2s;
  18. }
  19. .table-comment {
  20. font-size: 12px;
  21. color: #999;
  22. margin-top: 2px;
  23. }
  24. .main00 {
  25. width: 100%;
  26. height: 100vh;
  27. min-height: 600px;
  28. background: white;
  29. display: flex;
  30. flex-direction: column;
  31. overflow: hidden;
  32. }
  33. .ListTop {
  34. width: 100%;
  35. height: 60px;
  36. background: white;
  37. border-bottom: 1px solid #EEEEEE;
  38. justify-content: flex-start;
  39. flex-shrink: 0;
  40. }
  41. .ListTop3 {
  42. margin-left: 40px;
  43. height: 50px;
  44. align-items: center;
  45. }
  46. .title {
  47. font-size: 24px;
  48. color: #333333;
  49. font-weight: bold;
  50. }
  51. .main0 {
  52. width: 100%;
  53. background: white;
  54. flex: 1;
  55. min-height: 0;
  56. overflow: hidden;
  57. display: flex;
  58. position: relative;
  59. align-items: flex-start;
  60. }
  61. .tables-panel {
  62. width: 300px;
  63. border-right: 1px solid #EEEEEE;
  64. background: #F9F9F9;
  65. display: flex;
  66. flex-direction: column;
  67. height: 100%;
  68. position: relative;
  69. flex-shrink: 0;
  70. }
  71. .search-box {
  72. padding: 15px;
  73. border-bottom: 1px solid #EEEEEE;
  74. background: white;
  75. width: 100%;
  76. justify-content: center;
  77. }
  78. .search-input {
  79. width: 200px;
  80. height: 32px;
  81. padding: 0 32px 0 12px;
  82. border: 1px solid #DDDDDD;
  83. border-radius: 4px;
  84. font-size: 14px;
  85. color: #333333;
  86. }
  87. .search-input:focus {
  88. border-color: #4A90E2;
  89. outline: none;
  90. }
  91. .btn33 {
  92. width: 32px;
  93. height: 32px;
  94. margin-left: 8px;
  95. border-radius: 4px;
  96. background: #F5F5F5;
  97. cursor: pointer;
  98. justify-content: center;
  99. align-items: center;
  100. }
  101. .btn33:hover {
  102. background: #EEEEEE;
  103. }
  104. .content-panel {
  105. width: calc(100% - 300px);
  106. height: 100%;
  107. display: flex;
  108. flex-direction: column;
  109. overflow: hidden;
  110. }
  111. .sql-editor {
  112. padding: 20px;
  113. border-bottom: 1px solid #EEEEEE;
  114. background: white;
  115. height: 200px;
  116. display: flex;
  117. flex-direction: row;
  118. }
  119. .sql-editor-left {
  120. flex: 1;
  121. display: flex;
  122. flex-direction: column;
  123. margin-right: 20px;
  124. }
  125. .sql-editor-right {
  126. width: 300px;
  127. display: flex;
  128. flex-direction: column;
  129. border-left: 1px solid #EEEEEE;
  130. padding-left: 20px;
  131. position: relative;
  132. }
  133. .columns-list {
  134. flex: 1;
  135. overflow-y: auto;
  136. border: 1px solid #DDDDDD;
  137. border-radius: 4px;
  138. background: #FFFFFF;
  139. }
  140. .column-item {
  141. padding: 8px 10px;
  142. border-bottom: 1px solid #EEEEEE;
  143. cursor: pointer;
  144. display: flex;
  145. flex-direction: column;
  146. }
  147. .column-item:hover {
  148. background-color: #F0F7FF;
  149. }
  150. .column-name {
  151. font-weight: bold;
  152. }
  153. .column-type {
  154. font-size: 12px;
  155. color: #666;
  156. }
  157. .column-comment {
  158. font-size: 12px;
  159. color: #999;
  160. margin-top: 2px;
  161. }
  162. .sql-textarea {
  163. width: 100%;
  164. height: 120px;
  165. padding: 10px;
  166. border: 1px solid #DDDDDD;
  167. border-radius: 4px;
  168. font-family: monospace;
  169. font-size: 14px;
  170. resize: none;
  171. margin-bottom: 10px;
  172. }
  173. .sql-textarea:focus {
  174. border-color: #4A90E2;
  175. outline: none;
  176. }
  177. .results-panel {
  178. flex: 1;
  179. overflow: auto;
  180. padding: 20px;
  181. background: white;
  182. }
  183. .table-container {
  184. width: 100%;
  185. overflow-x: auto;
  186. }
  187. .data-table {
  188. width: 100%;
  189. border-collapse: collapse;
  190. font-size: 14px;
  191. }
  192. .data-table th {
  193. background: #F5F5F5;
  194. padding: 10px;
  195. text-align: left;
  196. border: 1px solid #DDDDDD;
  197. position: sticky;
  198. top: 0;
  199. z-index: 10;
  200. }
  201. .data-table td {
  202. padding: 8px 10px;
  203. border: 1px solid #DDDDDD;
  204. max-width: 300px;
  205. overflow: hidden;
  206. text-overflow: ellipsis;
  207. white-space: nowrap;
  208. }
  209. .data-table tr:nth-child(even) {
  210. background-color: #F9F9F9;
  211. }
  212. .data-table tr:hover {
  213. background-color: #F0F7FF;
  214. }
  215. .btn {
  216. min-width: 30px;
  217. height: 36px;
  218. padding: 0 16px;
  219. margin-left: 12px;
  220. border: none;
  221. border-radius: 4px;
  222. cursor: pointer;
  223. font-size: 14px;
  224. transition: all 0.3s;
  225. }
  226. .btn-primary {
  227. background: #4A90E2;
  228. color: white;
  229. }
  230. .btn-primary:hover {
  231. background: #357ABD;
  232. }
  233. .btn-default {
  234. background: white;
  235. border: 1px solid #DDDDDD;
  236. color: #666666;
  237. }
  238. .btn-default:hover {
  239. background: #F5F5F5;
  240. border-color: #CCCCCC;
  241. }
  242. .toast {
  243. position: fixed;
  244. top: 20px;
  245. left: 50%;
  246. transform: translateX(-50%);
  247. padding: 12px 24px;
  248. background: rgba(0, 0, 0, 0.7);
  249. color: white;
  250. border-radius: 4px;
  251. z-index: 9999;
  252. opacity: 0;
  253. transition: opacity 0.3s;
  254. }
  255. .toast.show {
  256. opacity: 1;
  257. }
  258. .toast.success {
  259. background: #4CAF50;
  260. }
  261. .toast.error {
  262. background: #F44336;
  263. }
  264. .toast.info {
  265. background: #2196F3;
  266. }
  267. /* Loading样式 */
  268. .loading-overlay {
  269. position: absolute;
  270. top: 0;
  271. left: 0;
  272. width: 100%;
  273. height: 100%;
  274. background-color: rgba(255, 255, 255, 0.7);
  275. display: flex;
  276. justify-content: center;
  277. align-items: center;
  278. z-index: 100;
  279. }
  280. .loading-spinner {
  281. width: 50px;
  282. height: 50px;
  283. border: 5px solid #f3f3f3;
  284. border-top: 5px solid #4A90E2;
  285. border-radius: 50%;
  286. animation: spin 1s linear infinite;
  287. }
  288. @keyframes spin {
  289. 0% { transform: rotate(0deg); }
  290. 100% { transform: rotate(360deg); }
  291. }
  292. .clear-btn {
  293. position: absolute;
  294. right: 12px;
  295. top: 50%;
  296. transform: translateY(-50%);
  297. width: 16px;
  298. height: 16px;
  299. background-color: #999;
  300. border-radius: 50%;
  301. display: flex;
  302. align-items: center;
  303. justify-content: center;
  304. cursor: pointer;
  305. transition: background-color 0.2s;
  306. }
  307. .clear-btn:hover {
  308. background-color: #666;
  309. }
  310. .clear-x {
  311. position: relative;
  312. width: 8px;
  313. height: 8px;
  314. }
  315. .clear-x:before,
  316. .clear-x:after {
  317. content: '';
  318. position: absolute;
  319. width: 8px;
  320. height: 2px;
  321. background-color: white;
  322. top: 3px;
  323. left: 0;
  324. }
  325. .clear-x:before {
  326. transform: rotate(45deg);
  327. }
  328. .clear-x:after {
  329. transform: rotate(-45deg);
  330. }
  331. .FlexRow {
  332. display: flex;
  333. flex-direction: row;
  334. }
  335. .FlexColumn {
  336. display: flex;
  337. flex-direction: column;
  338. }
  339. .table-list {
  340. flex: 1;
  341. overflow-y: auto;
  342. padding: 10px;
  343. }
  344. .table-item {
  345. padding: 10px;
  346. margin: 5px 0;
  347. background: white;
  348. border-radius: 4px;
  349. cursor: pointer;
  350. transition: background-color 0.2s;
  351. }
  352. .table-item:hover {
  353. background-color: #f0f0f0;
  354. }
  355. .table-item.active {
  356. background-color: #e6f3ff;
  357. border-left: 3px solid #4A90E2;
  358. }
  359. .btn-group {
  360. display: flex;
  361. justify-content: flex-end;
  362. margin-top: 10px;
  363. }
  364. .no-data {
  365. text-align: center;
  366. padding: 40px;
  367. color: #999;
  368. font-size: 16px;
  369. }
  370. .pagination {
  371. display: flex;
  372. justify-content: center;
  373. margin-top: 20px;
  374. padding: 10px;
  375. }
  376. .pagination-btn {
  377. padding: 5px 10px;
  378. margin: 0 5px;
  379. border: 1px solid #DDDDDD;
  380. border-radius: 4px;
  381. background: white;
  382. cursor: pointer;
  383. }
  384. .pagination-btn:hover {
  385. background: #F5F5F5;
  386. }
  387. .pagination-btn.active {
  388. background: #4A90E2;
  389. color: white;
  390. border-color: #4A90E2;
  391. }
  392. .pagination-btn.disabled {
  393. color: #CCCCCC;
  394. cursor: not-allowed;
  395. }
  396. </style>
  397. </head>
  398. <body class="container FlexRow">
  399. <div id="app" class="main00 FlexColumn">
  400. <div class="ListTop FlexRow">
  401. <div class="ListTop3 FlexRow" style="width: 100%; justify-content: space-between;">
  402. <div class="title">数据库管理</div>
  403. </div>
  404. </div>
  405. <div class="main0 FlexRow">
  406. <!-- 左侧表格列表 -->
  407. <div class="tables-panel" style="position: relative;">
  408. <div v-if="isTablesLoading" class="loading-overlay">
  409. <div class="loading-spinner"></div>
  410. </div>
  411. <div class="search-box FlexRow">
  412. <div style="position: relative;">
  413. <input type="text" class="search-input" v-model="searchText" @keyup.enter="searchTables"
  414. placeholder="搜索表格...">
  415. <div class="clear-btn" v-show="searchText" @click="clearSearch">
  416. <span class="clear-x"></span>
  417. </div>
  418. </div>
  419. <div class="btn33 FlexRow" @click="searchTables">
  420. <img title="搜索" alt="搜索"
  421. src="https://kylx365-1253256735.file.myqcloud.com/web/universalpic_search_gray_30x30.png"
  422. style="width: 20px; height: 20px;" />
  423. </div>
  424. </div>
  425. <!-- 表格列表 -->
  426. <div class="table-list">
  427. <div v-if="tables && tables.length > 0">
  428. <div v-for="(table, index) in filteredTables"
  429. :key="index"
  430. @click="selectTable(table)"
  431. :class="['table-item', { active: selectedTable === table }]">
  432. <div class="table-name">{{ table }}</div>
  433. <div class="table-comment" v-if="tableComments[table]">{{ tableComments[table] }}</div>
  434. </div>
  435. <div v-if="filteredTables.length === 0" class="no-data">
  436. 没有匹配的表格
  437. </div>
  438. </div>
  439. <div v-else-if="tables && tables.length === 0"
  440. class="no-data">
  441. 未找到表格
  442. </div>
  443. <div v-else class="no-data">
  444. 加载中...
  445. </div>
  446. </div>
  447. </div>
  448. <!-- 右侧内容区域 -->
  449. <div class="content-panel">
  450. <!-- SQL编辑器 -->
  451. <div class="sql-editor">
  452. <!-- 左侧SQL编辑区域 -->
  453. <div class="sql-editor-left">
  454. <textarea class="sql-textarea" v-model="sqlQuery" placeholder="输入SQL查询语句..."></textarea>
  455. <div class="btn-group">
  456. <button type="button" class="btn btn-default" @click="clearQuery">清空</button>
  457. <button type="button" class="btn btn-primary" @click="executeQuery">执行</button>
  458. </div>
  459. </div>
  460. <!-- 右侧字段列表 -->
  461. <div class="sql-editor-right">
  462. <div v-if="isColumnsLoading" class="loading-overlay">
  463. <div class="loading-spinner"></div>
  464. </div>
  465. <div class="columns-list">
  466. <div v-if="tableColumnsList && tableColumnsList.length > 0">
  467. <div v-for="(column, index) in tableColumnsList"
  468. :key="index"
  469. class="column-item"
  470. @click="insertColumnName(column.name)">
  471. <div class="column-name">{{ column.name }}</div>
  472. <div class="column-type">{{ column.type }}</div>
  473. <div class="column-comment" v-if="column.comment">{{ column.comment }}</div>
  474. </div>
  475. </div>
  476. <div v-else class="no-data">
  477. 请选择表格查看字段列表
  478. </div>
  479. </div>
  480. </div>
  481. </div>
  482. <!-- 查询结果 -->
  483. <div class="results-panel" style="position: relative;">
  484. <div v-if="isQueryLoading" class="loading-overlay">
  485. <div class="loading-spinner"></div>
  486. </div>
  487. <div v-if="queryResults && queryResults.length > 0" class="table-container">
  488. <table class="data-table">
  489. <thead>
  490. <tr>
  491. <th v-for="(column, index) in tableColumns" :key="index">{{ column }}</th>
  492. </tr>
  493. </thead>
  494. <tbody>
  495. <tr v-for="(row, rowIndex) in queryResults" :key="rowIndex">
  496. <td v-for="(column, colIndex) in tableColumns" :key="colIndex">
  497. {{ row[column] }}
  498. </td>
  499. </tr>
  500. </tbody>
  501. </table>
  502. <!-- 分页控件 -->
  503. <div class="pagination" v-if="totalPages > 1">
  504. <button class="pagination-btn"
  505. :class="{ disabled: currentPage === 1 }"
  506. @click="changePage(1)">
  507. 首页
  508. </button>
  509. <button class="pagination-btn"
  510. :class="{ disabled: currentPage === 1 }"
  511. @click="changePage(currentPage - 1)">
  512. 上一页
  513. </button>
  514. <button v-for="page in displayedPages"
  515. :key="page"
  516. class="pagination-btn"
  517. :class="{ active: currentPage === page }"
  518. @click="changePage(page)">
  519. {{ page }}
  520. </button>
  521. <button class="pagination-btn"
  522. :class="{ disabled: currentPage === totalPages }"
  523. @click="changePage(currentPage + 1)">
  524. 下一页
  525. </button>
  526. <button class="pagination-btn"
  527. :class="{ disabled: currentPage === totalPages }"
  528. @click="changePage(totalPages)">
  529. 末页
  530. </button>
  531. </div>
  532. </div>
  533. <div v-else-if="queryExecuted" class="no-data">
  534. 查询未返回数据
  535. </div>
  536. <div v-else class="no-data">
  537. 请选择表格并执行查询
  538. </div>
  539. </div>
  540. </div>
  541. </div>
  542. <!-- Toast提示 -->
  543. <div class="toast" :class="{ show: showToast, [toastType]: showToast }">{{ toastMessage }}</div>
  544. </div>
  545. <script>
  546. new Vue({
  547. el: '#app',
  548. data: {
  549. searchText: '',
  550. tables: [], // 存储表格名称列表
  551. tableComments: {}, // 存储表格注释,格式为 {表名: 注释}
  552. filteredTables: [],
  553. selectedTable: null,
  554. sqlQuery: '',
  555. queryResults: [],
  556. tableColumns: [],
  557. showToast: false,
  558. toastMessage: '',
  559. toastType: 'info',
  560. isTablesLoading: false,
  561. isQueryLoading: false,
  562. isColumnsLoading: false,
  563. queryExecuted: false,
  564. currentPage: 1,
  565. pageSize: 100,
  566. totalPages: 1,
  567. allResults: [],
  568. tableColumnsList: [] // 存储表格字段列表
  569. },
  570. computed: {
  571. displayedPages() {
  572. const pages = [];
  573. const maxVisiblePages = 5;
  574. let startPage = Math.max(1, this.currentPage - Math.floor(maxVisiblePages / 2));
  575. let endPage = Math.min(this.totalPages, startPage + maxVisiblePages - 1);
  576. if (endPage - startPage + 1 < maxVisiblePages) {
  577. startPage = Math.max(1, endPage - maxVisiblePages + 1);
  578. }
  579. for (let i = startPage; i <= endPage; i++) {
  580. pages.push(i);
  581. }
  582. return pages;
  583. }
  584. },
  585. mounted() {
  586. this.loadTables();
  587. },
  588. methods: {
  589. // 加载数据库表格列表
  590. loadTables() {
  591. this.isTablesLoading = true;
  592. this.tableComments = {};
  593. // 调用后端API获取表格列表
  594. fetch('/api/GetKylx365Tables')
  595. .then(response => {
  596. console.log('API响应状态:', response.status);
  597. if (!response.ok) {
  598. throw new Error(`HTTP error! status: ${response.status}`);
  599. }
  600. return response.json();
  601. })
  602. .then(data => {
  603. //console.log('API返回数据:', data);
  604. // 检查数据格式,处理可能的不同响应结构
  605. //console.log('检查数据格式:', data);
  606. // 尝试确定数据的实际结构
  607. let resultData = null;
  608. if (data && data.result && Array.isArray(data.result)) {
  609. //console.log('标准格式: data.result 是数组');
  610. resultData = data.result;
  611. } else if (data && Array.isArray(data)) {
  612. //console.log('替代格式: data 本身是数组');
  613. resultData = data;
  614. } else if (data && typeof data === 'object') {
  615. //console.log('检查对象中的数组属性');
  616. // 尝试在对象中找到数组属性
  617. for (const key in data) {
  618. if (Array.isArray(data[key])) {
  619. //console.log(`找到数组属性: ${key}`);
  620. resultData = data[key];
  621. break;
  622. }
  623. }
  624. }
  625. if (resultData && Array.isArray(resultData)) {
  626. //console.log('数据验证通过,开始处理数据');
  627. //console.log('数据结果长度:', resultData.length);
  628. if (resultData.length > 0 && typeof resultData[0] === 'object' && !Array.isArray(resultData[0])) {
  629. //console.log('检测到对象数组格式,第一个对象:', resultData[0]);
  630. //console.log('第一个对象的属性:', Object.keys(resultData[0]));
  631. // 检查并适应不同的属性名称
  632. const firstItem = resultData[0];
  633. const tableNameKey = 'table_name' in firstItem ? 'table_name' :
  634. 'tableName' in firstItem ? 'tableName' :
  635. 'name' in firstItem ? 'name' : null;
  636. const tableCommentKey = 'table_comment' in firstItem ? 'table_comment' :
  637. 'tableComment' in firstItem ? 'tableComment' :
  638. 'comment' in firstItem ? 'comment' : null;
  639. //console.log('使用的属性名:', { tableNameKey, tableCommentKey });
  640. if (tableNameKey) {
  641. // 如果返回的是对象数组(包含name和comment)
  642. this.tables = resultData.map(item => {
  643. const tableName = item[tableNameKey] || '';
  644. //console.log('处理表名:', tableName);
  645. return tableName;
  646. }).filter(name => name); // 过滤掉空表名
  647. //console.log('处理后的表格列表:', this.tables);
  648. // 存储表格注释
  649. if (tableCommentKey) {
  650. resultData.forEach(item => {
  651. const tableName = item[tableNameKey] || '';
  652. const tableComment = item[tableCommentKey] || '';
  653. if (tableName && tableComment) {
  654. this.tableComments[tableName] = tableComment;
  655. }
  656. });
  657. //console.log('处理后的表格注释:', this.tableComments);
  658. }
  659. } else {
  660. //console.error('无法找到表名属性');
  661. this.showToastMessage('数据格式错误:无法找到表名属性', 'error');
  662. }
  663. } else {
  664. //console.log('检测到简单数组格式');
  665. // 如果返回的只是表名数组
  666. this.tables = resultData.filter(name => {
  667. //console.log('处理表名:', name);
  668. return name && typeof name === 'string';
  669. });
  670. //console.log('处理后的表格列表:', this.tables);
  671. }
  672. this.filteredTables = [...this.tables];
  673. // console.log('更新后的过滤表格列表:', this.filteredTables);
  674. } else {
  675. this.tables = [];
  676. this.filteredTables = [];
  677. this.showToastMessage('获取表格列表失败', 'error');
  678. }
  679. this.isTablesLoading = false;
  680. })
  681. .catch(error => {
  682. //console.error('获取表格列表失败详细错误:', error);
  683. this.showToastMessage(`获取表格列表失败: ${error.message}`, 'error');
  684. this.isTablesLoading = false;
  685. // 检查是否在本地环境
  686. if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
  687. //console.log('在本地环境中使用模拟数据');
  688. // 模拟数据用于开发测试
  689. this.tables = [
  690. 'users', 'products', 'orders', 'categories',
  691. 'customers', 'suppliers', 'inventory', 'payments',
  692. 'shipping', 'reviews', 'logs', 'settings'
  693. ];
  694. // 模拟表格注释
  695. this.tableComments = {
  696. 'users': '用户信息表',
  697. 'products': '产品信息表',
  698. 'orders': '订单信息表',
  699. 'categories': '分类信息表',
  700. 'customers': '客户信息表'
  701. };
  702. this.filteredTables = [...this.tables];
  703. }
  704. });
  705. },
  706. // 搜索表格
  707. searchTables() {
  708. //console.log('开始搜索表格,搜索文本:', this.searchText);
  709. if (!this.searchText.trim()) {
  710. //console.log('搜索文本为空,显示所有表格');
  711. this.filteredTables = [...this.tables];
  712. return;
  713. }
  714. const searchTerm = this.searchText.toLowerCase();
  715. //console.log('过滤表格列表,搜索条件:', searchTerm);
  716. this.filteredTables = this.tables.filter(table => {
  717. const tableName = table.toLowerCase();
  718. const tableComment = (this.tableComments[table] || '').toLowerCase();
  719. const matches = tableName.includes(searchTerm) || tableComment.includes(searchTerm);
  720. // if (matches) {
  721. // console.log(`表格匹配: ${table} (${this.tableComments[table] || '无注释'})`);
  722. // }
  723. return matches;
  724. });
  725. //console.log('过滤后的表格数量:', this.filteredTables.length);
  726. },
  727. // 清空搜索
  728. clearSearch() {
  729. this.searchText = '';
  730. this.filteredTables = [...this.tables];
  731. },
  732. // 选择表格
  733. selectTable(table) {
  734. this.selectedTable = table;
  735. this.sqlQuery = `SELECT * FROM ${table} LIMIT 100;`;
  736. // 获取表格字段列表
  737. this.loadTableColumns(table);
  738. },
  739. // 加载表格字段列表
  740. loadTableColumns(tableName) {
  741. this.isColumnsLoading = true;
  742. this.tableColumnsList = [];
  743. // 调用API获取表格字段列表
  744. fetch(`/api/GetKylx365TableColumnByTable?table=${encodeURIComponent(tableName)}`)
  745. .then(response => response.json())
  746. .then(data => {
  747. console.log('收到的原始数据:', data);
  748. // 验证数据格式
  749. if (!data) {
  750. console.error('接收到空数据');
  751. throw new Error('接收到空数据');
  752. }
  753. // 尝试确定数据的实际结构
  754. let columnsData = null;
  755. if (data && data.result && Array.isArray(data.result)) {
  756. console.log('标准格式: data.result 是数组');
  757. columnsData = data.result;
  758. } else if (data && Array.isArray(data)) {
  759. console.log('替代格式: data 本身是数组');
  760. columnsData = data;
  761. } else if (data && typeof data === 'object') {
  762. console.log('检查对象中的数组属性');
  763. // 尝试在对象中找到数组属性
  764. for (const key in data) {
  765. if (Array.isArray(data[key])) {
  766. console.log(`找到数组属性: ${key}`);
  767. columnsData = data[key];
  768. break;
  769. }
  770. }
  771. }
  772. if (columnsData && Array.isArray(columnsData)) {
  773. console.log('数据验证通过,开始处理数据');
  774. console.log('字段数据长度:', columnsData.length);
  775. if (columnsData.length > 0) {
  776. // 检查第一个元素的格式
  777. const firstItem = columnsData[0];
  778. console.log('第一个字段项:', firstItem);
  779. if (typeof firstItem === 'object' && !Array.isArray(firstItem)) {
  780. // 检查并适应不同的属性名称
  781. const nameKey = 'Field' in firstItem ? 'Field' :
  782. 'name' in firstItem ? 'name' :
  783. 'column_name' in firstItem ? 'column_name' :
  784. 'columnName' in firstItem ? 'columnName' :
  785. 'field' in firstItem ? 'field' : null;
  786. const typeKey = 'Type' in firstItem ? 'Type' :
  787. 'type' in firstItem ? 'type' :
  788. 'column_type' in firstItem ? 'column_type' :
  789. 'columnType' in firstItem ? 'columnType' :
  790. 'data_type' in firstItem ? 'data_type' : null;
  791. const commentKey = 'Comment' in firstItem ? 'Comment' :
  792. 'comment' in firstItem ? 'comment' :
  793. 'column_comment' in firstItem ? 'column_comment' :
  794. 'columnComment' in firstItem ? 'columnComment' : null;
  795. // 组合Default和Extra作为注释
  796. const getComment = (item) => {
  797. let comment = [];
  798. if (item['Default']) {
  799. comment.push(`默认值: ${item['Default']}`);
  800. }
  801. if (item['Extra'] && item['Extra'] !== '') {
  802. comment.push(item['Extra']);
  803. }
  804. if (item['Null'] === 'NO') {
  805. comment.push('不可为空');
  806. }
  807. return comment.join(', ');
  808. };
  809. console.log('使用的属性名:', { nameKey, typeKey, commentKey });
  810. if (nameKey) {
  811. // 转换为标准格式
  812. this.tableColumnsList = columnsData.map(item => {
  813. return {
  814. name: item[nameKey] || '',
  815. type: typeKey ? (item[typeKey] || '') : '',
  816. comment: [
  817. commentKey ? (item[commentKey] || '') : '',
  818. getComment(item)
  819. ].filter(Boolean).join(' | ')
  820. };
  821. }).filter(col => col.name); // 过滤掉没有名称的列
  822. console.log('处理后的字段列表:', this.tableColumnsList);
  823. } else {
  824. console.error('无法找到字段名属性');
  825. this.showToastMessage('数据格式错误:无法找到字段名属性', 'error');
  826. this.tableColumnsList = [];
  827. }
  828. } else if (typeof firstItem === 'string') {
  829. // 如果只是字段名数组
  830. this.tableColumnsList = columnsData.map(name => {
  831. return {
  832. name: name,
  833. type: '',
  834. comment: ''
  835. };
  836. });
  837. console.log('处理后的字段列表(仅名称):', this.tableColumnsList);
  838. } else {
  839. console.error('未知的字段数据格式');
  840. this.showToastMessage('未知的字段数据格式', 'error');
  841. this.tableColumnsList = [];
  842. }
  843. } else {
  844. console.log('字段列表为空');
  845. this.tableColumnsList = [];
  846. }
  847. } else {
  848. console.error('无法找到有效的字段数据');
  849. this.tableColumnsList = [];
  850. this.showToastMessage('获取字段列表失败', 'error');
  851. }
  852. this.isColumnsLoading = false;
  853. })
  854. .catch(error => {
  855. this.showToastMessage('获取字段列表失败,请稍后重试', 'error');
  856. this.isColumnsLoading = false;
  857. // 模拟数据用于开发测试
  858. if (tableName === 'users') {
  859. this.tableColumnsList = [
  860. { name: 'id', type: 'int(11)', comment: '用户ID' },
  861. { name: 'username', type: 'varchar(50)', comment: '用户名' },
  862. { name: 'email', type: 'varchar(100)', comment: '电子邮箱' },
  863. { name: 'password', type: 'varchar(255)', comment: '密码' },
  864. { name: 'created_at', type: 'datetime', comment: '创建时间' },
  865. { name: 'status', type: 'tinyint(1)', comment: '状态' }
  866. ];
  867. } else if (tableName === 'products') {
  868. this.tableColumnsList = [
  869. { name: 'id', type: 'int(11)', comment: '产品ID' },
  870. { name: 'name', type: 'varchar(100)', comment: '产品名称' },
  871. { name: 'price', type: 'decimal(10,2)', comment: '价格' },
  872. { name: 'category_id', type: 'int(11)', comment: '分类ID' },
  873. { name: 'stock', type: 'int(11)', comment: '库存' },
  874. { name: 'description', type: 'text', comment: '产品描述' }
  875. ];
  876. } else {
  877. this.tableColumnsList = [
  878. { name: 'id', type: 'int(11)', comment: '主键ID' },
  879. { name: 'name', type: 'varchar(100)', comment: '名称' },
  880. { name: 'description', type: 'text', comment: '描述' },
  881. { name: 'created_at', type: 'datetime', comment: '创建时间' }
  882. ];
  883. }
  884. });
  885. },
  886. // 清空查询
  887. clearQuery() {
  888. this.sqlQuery = '';
  889. },
  890. // 插入字段名称到SQL查询
  891. insertColumnName(columnName) {
  892. // 获取文本框元素
  893. const textarea = document.querySelector('.sql-textarea');
  894. // 如果文本框存在
  895. if (textarea) {
  896. // 获取当前光标位置
  897. const startPos = textarea.selectionStart;
  898. const endPos = textarea.selectionEnd;
  899. // 在光标位置插入字段名称
  900. const textBefore = this.sqlQuery.substring(0, startPos);
  901. const textAfter = this.sqlQuery.substring(endPos);
  902. // 更新SQL查询
  903. this.sqlQuery = textBefore + columnName + textAfter;
  904. // 设置新的光标位置
  905. this.$nextTick(() => {
  906. textarea.focus();
  907. textarea.selectionStart = startPos + columnName.length;
  908. textarea.selectionEnd = startPos + columnName.length;
  909. });
  910. } else {
  911. // 如果无法获取文本框元素,则直接在末尾添加
  912. this.sqlQuery += ' ' + columnName;
  913. }
  914. },
  915. // 执行查询
  916. executeQuery() {
  917. if (!this.sqlQuery.trim()) {
  918. this.showToastMessage('请输入SQL查询语句', 'info');
  919. return;
  920. }
  921. this.isQueryLoading = true;
  922. this.queryExecuted = true;
  923. this.currentPage = 1;
  924. // 这里应该调用后端API执行SQL查询
  925. fetch('/api/ExecuteSqlQuery', {
  926. method: 'POST',
  927. headers: {
  928. 'Content-Type': 'application/json'
  929. },
  930. body: JSON.stringify({
  931. query: this.sqlQuery
  932. })
  933. })
  934. .then(response => response.json())
  935. .then(data => {
  936. if (data && data.result) {
  937. this.processQueryResults(data.result);
  938. } else {
  939. this.queryResults = [];
  940. this.tableColumns = [];
  941. this.showToastMessage('查询未返回数据', 'info');
  942. }
  943. this.isQueryLoading = false;
  944. })
  945. .catch(error => {
  946. this.showToastMessage('执行查询失败,请稍后重试', 'error');
  947. this.isQueryLoading = false;
  948. // 模拟数据用于开发测试
  949. if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
  950. console.log('在本地环境中使用模拟数据');
  951. // 根据选择的表格生成模拟数据
  952. let mockData = [];
  953. if (this.selectedTable === 'users') {
  954. mockData = [
  955. { id: 1, username: 'admin', email: 'admin@example.com', password: '******', created_at: '2023-01-01 00:00:00', status: 1 },
  956. { id: 2, username: 'user1', email: 'user1@example.com', password: '******', created_at: '2023-01-02 10:30:00', status: 1 },
  957. { id: 3, username: 'user2', email: 'user2@example.com', password: '******', created_at: '2023-01-03 15:45:00', status: 0 }
  958. ];
  959. } else if (this.selectedTable === 'products') {
  960. mockData = [
  961. { id: 1, name: '产品A', price: '99.99', category_id: 1, stock: 100, description: '这是产品A的描述' },
  962. { id: 2, name: '产品B', price: '199.99', category_id: 2, stock: 50, description: '这是产品B的描述' },
  963. { id: 3, name: '产品C', price: '299.99', category_id: 1, stock: 75, description: '这是产品C的描述' }
  964. ];
  965. } else {
  966. // 生成通用模拟数据
  967. for (let i = 1; i <= 10; i++) {
  968. mockData.push({
  969. id: i,
  970. name: `示例名称 ${i}`,
  971. description: `这是示例描述 ${i}`,
  972. created_at: new Date().toISOString().slice(0, 19).replace('T', ' ')
  973. });
  974. }
  975. }
  976. this.processQueryResults(mockData);
  977. }
  978. });
  979. },
  980. // 处理查询结果
  981. processQueryResults(results) {
  982. if (!results || !Array.isArray(results) || results.length === 0) {
  983. this.queryResults = [];
  984. this.tableColumns = [];
  985. this.allResults = [];
  986. this.totalPages = 1;
  987. return;
  988. }
  989. // 存储所有结果
  990. this.allResults = [...results];
  991. // 提取表格列
  992. this.tableColumns = Object.keys(results[0]);
  993. // 计算总页数
  994. this.totalPages = Math.ceil(results.length / this.pageSize);
  995. // 显示第一页数据
  996. this.changePage(1);
  997. this.showToastMessage(`查询成功,返回 ${results.length} 条记录`, 'success');
  998. },
  999. // 切换页面
  1000. changePage(page) {
  1001. if (page < 1 || page > this.totalPages || page === this.currentPage) {
  1002. return;
  1003. }
  1004. this.currentPage = page;
  1005. // 计算当前页的数据
  1006. const startIndex = (page - 1) * this.pageSize;
  1007. const endIndex = Math.min(startIndex + this.pageSize, this.allResults.length);
  1008. this.queryResults = this.allResults.slice(startIndex, endIndex);
  1009. },
  1010. // 显示提示消息
  1011. showToastMessage(message, type = 'info') {
  1012. this.toastMessage = message;
  1013. this.toastType = type;
  1014. this.showToast = true;
  1015. // 3秒后自动隐藏
  1016. setTimeout(() => {
  1017. this.showToast = false;
  1018. }, 3000);
  1019. }
  1020. }
  1021. });
  1022. </script>
  1023. </body>
  1024. </html>