|
|
@@ -78,11 +78,12 @@
|
|
78
|
78
|
}
|
|
79
|
79
|
|
|
80
|
80
|
.search-box {
|
|
81
|
|
- padding: 15px;
|
|
|
81
|
+ padding: 15px 0;
|
|
82
|
82
|
border-bottom: 1px solid #EEEEEE;
|
|
83
|
83
|
background: white;
|
|
84
|
84
|
width: 100%;
|
|
85
|
85
|
justify-content: center;
|
|
|
86
|
+ width: 100%;
|
|
86
|
87
|
}
|
|
87
|
88
|
|
|
88
|
89
|
.search-input {
|
|
|
@@ -590,7 +591,7 @@
|
|
590
|
591
|
</div>
|
|
591
|
592
|
<div class="search-box FlexRow">
|
|
592
|
593
|
<div style="position: relative;">
|
|
593
|
|
- <input type="text" class="search-input" v-model="searchText" @keyup.enter="searchTables"
|
|
|
594
|
+ <input type="text" class="search-input" v-model="searchText" @input="searchTables" @keyup.enter="searchTables"
|
|
594
|
595
|
placeholder="搜索表格...">
|
|
595
|
596
|
<div class="clear-btn" v-show="searchText" @click="clearSearch">
|
|
596
|
597
|
<span class="clear-x"></span>
|
|
|
@@ -630,15 +631,20 @@
|
|
630
|
631
|
<!-- 左侧SQL编辑区域 -->
|
|
631
|
632
|
<div class="sql-editor-left">
|
|
632
|
633
|
<textarea class="sql-textarea" v-model="sqlQuery" placeholder="输入SQL查询语句..."></textarea>
|
|
633
|
|
- <div class="btn-group">
|
|
|
634
|
+ <div class="btn-group" style="justify-content: flex-start;">
|
|
|
635
|
+ <button type="button" class="btn btn-default" @click="insertSqlClause('GROUP BY')">GROUP BY</button>
|
|
|
636
|
+ <button type="button" class="btn btn-default" @click="insertSqlClause('HAVING')">HAVING</button>
|
|
|
637
|
+ <button type="button" class="btn btn-default" @click="insertSqlClause('ORDER BY')">ORDER BY</button>
|
|
634
|
638
|
<select class="btn btn-default" v-model="selectedLimit" @change="updateQueryLimit"
|
|
635
|
|
- style="margin-right: 8px;">
|
|
|
639
|
+ style="margin-left: 8px;">
|
|
636
|
640
|
<option v-for="limit in limitOptions" :key="limit" :value="limit">
|
|
637
|
641
|
LIMIT {{ limit }}
|
|
638
|
642
|
</option>
|
|
639
|
643
|
</select>
|
|
640
|
|
- <button type="button" class="btn btn-default" @click="clearQuery">清空</button>
|
|
641
|
|
- <button type="button" class="btn btn-primary" @click="executeQuery">执行</button>
|
|
|
644
|
+ <div style="margin-left: auto;">
|
|
|
645
|
+ <button type="button" class="btn btn-default" @click="clearQuery">清空</button>
|
|
|
646
|
+ <button type="button" class="btn btn-primary" @click="executeQuery">执行</button>
|
|
|
647
|
+ </div>
|
|
642
|
648
|
</div>
|
|
643
|
649
|
</div>
|
|
644
|
650
|
|
|
|
@@ -932,6 +938,9 @@
|
|
932
|
938
|
selectTable(table) {
|
|
933
|
939
|
this.selectedTable = table;
|
|
934
|
940
|
|
|
|
941
|
+ // 重置LIMIT值为默认的100
|
|
|
942
|
+ this.selectedLimit = 100;
|
|
|
943
|
+
|
|
935
|
944
|
// 获取表格字段列表
|
|
936
|
945
|
this.loadTableColumns(table);
|
|
937
|
946
|
|
|
|
@@ -939,9 +948,9 @@
|
|
939
|
948
|
this.$watch('tableColumnsList', (newVal) => {
|
|
940
|
949
|
if (newVal && newVal.length > 0) {
|
|
941
|
950
|
const columns = newVal.map(col => `\`${col.name}\``).join(',');
|
|
942
|
|
- this.sqlQuery = `SELECT ${columns} FROM \`${table}\` LIMIT 100;`;
|
|
|
951
|
+ this.sqlQuery = `SELECT ${columns}\nFROM \`${table}\`\nLIMIT ${this.selectedLimit};`;
|
|
943
|
952
|
} else {
|
|
944
|
|
- this.sqlQuery = `SELECT * FROM \`${table}\` LIMIT 100;`;
|
|
|
953
|
+ this.sqlQuery = `SELECT *\nFROM \`${table}\`\nLIMIT ${this.selectedLimit};`;
|
|
945
|
954
|
}
|
|
946
|
955
|
}, {immediate: true});
|
|
947
|
956
|
},
|
|
|
@@ -1173,10 +1182,10 @@
|
|
1173
|
1182
|
// 检查SQL是否以分号结尾
|
|
1174
|
1183
|
if (this.sqlQuery.trim().endsWith(';')) {
|
|
1175
|
1184
|
// 在分号前添加LIMIT
|
|
1176
|
|
- this.sqlQuery = this.sqlQuery.replace(/;\s*$/, ` LIMIT ${this.selectedLimit};`);
|
|
|
1185
|
+ this.sqlQuery = this.sqlQuery.replace(/;\s*$/, `\nLIMIT ${this.selectedLimit};`);
|
|
1177
|
1186
|
} else {
|
|
1178
|
1187
|
// 直接在末尾添加LIMIT
|
|
1179
|
|
- this.sqlQuery = this.sqlQuery.trim() + ` LIMIT ${this.selectedLimit};`;
|
|
|
1188
|
+ this.sqlQuery = this.sqlQuery.trim() + `\nLIMIT ${this.selectedLimit};`;
|
|
1180
|
1189
|
}
|
|
1181
|
1190
|
}
|
|
1182
|
1191
|
|
|
|
@@ -1443,6 +1452,187 @@
|
|
1443
|
1452
|
});
|
|
1444
|
1453
|
},
|
|
1445
|
1454
|
|
|
|
1455
|
+ // 插入SQL子句(ORDER BY, GROUP BY, HAVING等)
|
|
|
1456
|
+ insertSqlClause(clause) {
|
|
|
1457
|
+ // 检查SQL查询是否为空
|
|
|
1458
|
+ if (!this.sqlQuery.trim()) {
|
|
|
1459
|
+ this.sqlQuery = clause + ' ';
|
|
|
1460
|
+ return;
|
|
|
1461
|
+ }
|
|
|
1462
|
+
|
|
|
1463
|
+ // 检查子句是否已存在(不区分大小写)
|
|
|
1464
|
+ const sqlUpper = this.sqlQuery.toUpperCase();
|
|
|
1465
|
+ const clauseUpper = clause.toUpperCase();
|
|
|
1466
|
+
|
|
|
1467
|
+ if (sqlUpper.includes(clauseUpper)) {
|
|
|
1468
|
+ this.showToastMessage(`查询中已存在 "${clause}" 子句`, 'info');
|
|
|
1469
|
+ return;
|
|
|
1470
|
+ }
|
|
|
1471
|
+
|
|
|
1472
|
+ // 解析SQL查询,识别各个子句的位置
|
|
|
1473
|
+ const sqlParts = {
|
|
|
1474
|
+ select: -1,
|
|
|
1475
|
+ from: -1,
|
|
|
1476
|
+ where: -1,
|
|
|
1477
|
+ groupBy: -1,
|
|
|
1478
|
+ having: -1,
|
|
|
1479
|
+ orderBy: -1,
|
|
|
1480
|
+ limit: -1
|
|
|
1481
|
+ };
|
|
|
1482
|
+
|
|
|
1483
|
+ // 查找各个子句的位置
|
|
|
1484
|
+ sqlParts.select = sqlUpper.indexOf('SELECT');
|
|
|
1485
|
+ sqlParts.from = sqlUpper.indexOf('FROM', sqlParts.select);
|
|
|
1486
|
+ sqlParts.where = sqlUpper.indexOf('WHERE', sqlParts.from);
|
|
|
1487
|
+ sqlParts.groupBy = sqlUpper.indexOf('GROUP BY', sqlParts.from);
|
|
|
1488
|
+ sqlParts.having = sqlUpper.indexOf('HAVING', sqlParts.from);
|
|
|
1489
|
+ sqlParts.orderBy = sqlUpper.indexOf('ORDER BY', sqlParts.from);
|
|
|
1490
|
+
|
|
|
1491
|
+ // 查找LIMIT子句(可能在末尾或分号前)
|
|
|
1492
|
+ const limitMatch = sqlUpper.match(/LIMIT\s+\d+/i);
|
|
|
1493
|
+ if (limitMatch) {
|
|
|
1494
|
+ sqlParts.limit = limitMatch.index;
|
|
|
1495
|
+ }
|
|
|
1496
|
+
|
|
|
1497
|
+ // 确定插入位置
|
|
|
1498
|
+ let insertPosition = -1;
|
|
|
1499
|
+ let insertBefore = '';
|
|
|
1500
|
+
|
|
|
1501
|
+ switch (clauseUpper) {
|
|
|
1502
|
+ case 'GROUP BY':
|
|
|
1503
|
+ // GROUP BY应该在WHERE之后,HAVING/ORDER BY/LIMIT之前
|
|
|
1504
|
+ if (sqlParts.where > -1) {
|
|
|
1505
|
+ // 找到WHERE子句后的位置
|
|
|
1506
|
+ const whereEndPos = this.findClauseEndPosition(sqlUpper, sqlParts.where);
|
|
|
1507
|
+ insertPosition = whereEndPos;
|
|
|
1508
|
+ } else if (sqlParts.having > -1) {
|
|
|
1509
|
+ // 如果没有WHERE但有HAVING,插入在HAVING之前
|
|
|
1510
|
+ insertPosition = sqlParts.having;
|
|
|
1511
|
+ insertBefore = 'HAVING';
|
|
|
1512
|
+ } else if (sqlParts.orderBy > -1) {
|
|
|
1513
|
+ // 如果没有WHERE和HAVING但有ORDER BY,插入在ORDER BY之前
|
|
|
1514
|
+ insertPosition = sqlParts.orderBy;
|
|
|
1515
|
+ insertBefore = 'ORDER BY';
|
|
|
1516
|
+ } else if (sqlParts.limit > -1) {
|
|
|
1517
|
+ // 如果只有LIMIT,插入在LIMIT之前
|
|
|
1518
|
+ insertPosition = sqlParts.limit;
|
|
|
1519
|
+ insertBefore = 'LIMIT';
|
|
|
1520
|
+ } else {
|
|
|
1521
|
+ // 如果没有以上子句,插入在查询末尾(可能有分号)
|
|
|
1522
|
+ insertPosition = this.sqlQuery.length;
|
|
|
1523
|
+ if (this.sqlQuery.trim().endsWith(';')) {
|
|
|
1524
|
+ insertPosition = this.sqlQuery.lastIndexOf(';');
|
|
|
1525
|
+ }
|
|
|
1526
|
+ }
|
|
|
1527
|
+ break;
|
|
|
1528
|
+
|
|
|
1529
|
+ case 'HAVING':
|
|
|
1530
|
+ // HAVING应该在GROUP BY之后,ORDER BY/LIMIT之前
|
|
|
1531
|
+ if (sqlParts.groupBy > -1) {
|
|
|
1532
|
+ // 找到GROUP BY子句后的位置
|
|
|
1533
|
+ const groupByEndPos = this.findClauseEndPosition(sqlUpper, sqlParts.groupBy);
|
|
|
1534
|
+ insertPosition = groupByEndPos;
|
|
|
1535
|
+ } else if (sqlParts.orderBy > -1) {
|
|
|
1536
|
+ // 如果没有GROUP BY但有ORDER BY,插入在ORDER BY之前
|
|
|
1537
|
+ insertPosition = sqlParts.orderBy;
|
|
|
1538
|
+ insertBefore = 'ORDER BY';
|
|
|
1539
|
+ } else if (sqlParts.limit > -1) {
|
|
|
1540
|
+ // 如果只有LIMIT,插入在LIMIT之前
|
|
|
1541
|
+ insertPosition = sqlParts.limit;
|
|
|
1542
|
+ insertBefore = 'LIMIT';
|
|
|
1543
|
+ } else {
|
|
|
1544
|
+ // 如果没有以上子句,插入在查询末尾(可能有分号)
|
|
|
1545
|
+ insertPosition = this.sqlQuery.length;
|
|
|
1546
|
+ if (this.sqlQuery.trim().endsWith(';')) {
|
|
|
1547
|
+ insertPosition = this.sqlQuery.lastIndexOf(';');
|
|
|
1548
|
+ }
|
|
|
1549
|
+ }
|
|
|
1550
|
+ break;
|
|
|
1551
|
+
|
|
|
1552
|
+ case 'ORDER BY':
|
|
|
1553
|
+ // ORDER BY应该在GROUP BY和HAVING之后,LIMIT之前
|
|
|
1554
|
+ if (sqlParts.having > -1) {
|
|
|
1555
|
+ // 找到HAVING子句后的位置
|
|
|
1556
|
+ const havingEndPos = this.findClauseEndPosition(sqlUpper, sqlParts.having);
|
|
|
1557
|
+ insertPosition = havingEndPos;
|
|
|
1558
|
+ } else if (sqlParts.groupBy > -1) {
|
|
|
1559
|
+ // 如果没有HAVING但有GROUP BY,找到GROUP BY子句后的位置
|
|
|
1560
|
+ const groupByEndPos = this.findClauseEndPosition(sqlUpper, sqlParts.groupBy);
|
|
|
1561
|
+ insertPosition = groupByEndPos;
|
|
|
1562
|
+ } else if (sqlParts.limit > -1) {
|
|
|
1563
|
+ // 如果只有LIMIT,插入在LIMIT之前
|
|
|
1564
|
+ insertPosition = sqlParts.limit;
|
|
|
1565
|
+ insertBefore = 'LIMIT';
|
|
|
1566
|
+ } else {
|
|
|
1567
|
+ // 如果没有以上子句,插入在查询末尾(可能有分号)
|
|
|
1568
|
+ insertPosition = this.sqlQuery.length;
|
|
|
1569
|
+ if (this.sqlQuery.trim().endsWith(';')) {
|
|
|
1570
|
+ insertPosition = this.sqlQuery.lastIndexOf(';');
|
|
|
1571
|
+ }
|
|
|
1572
|
+ }
|
|
|
1573
|
+ break;
|
|
|
1574
|
+ }
|
|
|
1575
|
+
|
|
|
1576
|
+ // 执行插入
|
|
|
1577
|
+ if (insertPosition > -1) {
|
|
|
1578
|
+ let newQuery = '';
|
|
|
1579
|
+ if (insertBefore) {
|
|
|
1580
|
+ // 在特定子句之前插入
|
|
|
1581
|
+ const beforeInsert = this.sqlQuery.substring(0, insertPosition);
|
|
|
1582
|
+ const afterInsert = this.sqlQuery.substring(insertPosition);
|
|
|
1583
|
+
|
|
|
1584
|
+ // 添加换行符和子句
|
|
|
1585
|
+ newQuery = beforeInsert + '\n' + clause + ' ' + afterInsert;
|
|
|
1586
|
+ } else {
|
|
|
1587
|
+ // 在子句末尾插入
|
|
|
1588
|
+ const beforeInsert = this.sqlQuery.substring(0, insertPosition);
|
|
|
1589
|
+ const afterInsert = this.sqlQuery.substring(insertPosition);
|
|
|
1590
|
+
|
|
|
1591
|
+ // 添加换行符和子句
|
|
|
1592
|
+ newQuery = beforeInsert + '\n' + clause + ' ' + afterInsert;
|
|
|
1593
|
+ }
|
|
|
1594
|
+
|
|
|
1595
|
+ this.sqlQuery = newQuery;
|
|
|
1596
|
+ this.showToastMessage(`已插入 "${clause}" 子句到查询`, 'success');
|
|
|
1597
|
+
|
|
|
1598
|
+ // 设置光标位置到插入的子句之后
|
|
|
1599
|
+ this.$nextTick(() => {
|
|
|
1600
|
+ const textarea = document.querySelector('.sql-textarea');
|
|
|
1601
|
+ if (textarea) {
|
|
|
1602
|
+ textarea.focus();
|
|
|
1603
|
+ const newPosition = insertPosition + clause.length + 1;
|
|
|
1604
|
+ textarea.selectionStart = newPosition;
|
|
|
1605
|
+ textarea.selectionEnd = newPosition;
|
|
|
1606
|
+ }
|
|
|
1607
|
+ });
|
|
|
1608
|
+ } else {
|
|
|
1609
|
+ // 如果无法确定位置,则在末尾添加
|
|
|
1610
|
+ if (this.sqlQuery.trim().endsWith(';')) {
|
|
|
1611
|
+ this.sqlQuery = this.sqlQuery.replace(/;\s*$/, `\n${clause} ;`);
|
|
|
1612
|
+ } else {
|
|
|
1613
|
+ this.sqlQuery += `\n${clause} `;
|
|
|
1614
|
+ }
|
|
|
1615
|
+ this.showToastMessage(`已插入 "${clause}" 子句到查询末尾`, 'info');
|
|
|
1616
|
+ }
|
|
|
1617
|
+ },
|
|
|
1618
|
+
|
|
|
1619
|
+ // 查找子句结束位置的辅助方法
|
|
|
1620
|
+ findClauseEndPosition(sqlUpper, clauseStartPos) {
|
|
|
1621
|
+ // 子句可能的结束标记
|
|
|
1622
|
+ const endMarkers = ['GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', ';'];
|
|
|
1623
|
+ let endPos = sqlUpper.length;
|
|
|
1624
|
+
|
|
|
1625
|
+ // 查找最近的下一个子句
|
|
|
1626
|
+ for (const marker of endMarkers) {
|
|
|
1627
|
+ const pos = sqlUpper.indexOf(marker, clauseStartPos + 1);
|
|
|
1628
|
+ if (pos > -1 && pos < endPos) {
|
|
|
1629
|
+ endPos = pos;
|
|
|
1630
|
+ }
|
|
|
1631
|
+ }
|
|
|
1632
|
+
|
|
|
1633
|
+ return endPos;
|
|
|
1634
|
+ },
|
|
|
1635
|
+
|
|
1446
|
1636
|
// 显示提示消息
|
|
1447
|
1637
|
showToastMessage(message, type = 'info') {
|
|
1448
|
1638
|
this.toastMessage = message;
|