最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot與Vue開發(fā)Web版SQL執(zhí)行工具

 更新時(shí)間:2025年08月15日 09:40:30   作者:Derek_Smart  
這篇文章主要為大家詳細(xì)介紹了一個(gè)基于Web的SQL執(zhí)行工具的設(shè)計(jì)與實(shí)現(xiàn),該工具允許用戶通過瀏覽器直接執(zhí)行SQL語句并查看結(jié)果,需要的小伙伴可以了解下

簡介

本文介紹了一個(gè)基于Web的SQL執(zhí)行工具的設(shè)計(jì)與實(shí)現(xiàn),該工具允許用戶通過瀏覽器直接執(zhí)行SQL語句并查看結(jié)果。系統(tǒng)采用前后端分離架構(gòu),后端基于Spring Boot框架實(shí)現(xiàn)SQL執(zhí)行引擎,前端使用Vue.js構(gòu)建用戶界面。該系統(tǒng)提供了直觀的SQL編輯器、數(shù)據(jù)庫表管理、歷史查詢記錄和結(jié)果展示等功能,有效提高了數(shù)據(jù)庫操作效率。

(本質(zhì)就是客戶端不知為何連接不上服務(wù)器的數(shù)據(jù)庫,緊急情況下,手搓了一個(gè)sql 執(zhí)行器)

本文介紹的網(wǎng)頁版SQL執(zhí)行工具,通過整合Spring Boot后端與Vue前端,實(shí)現(xiàn)了數(shù)據(jù)庫管理的范式轉(zhuǎn)移,將復(fù)雜操作轉(zhuǎn)化為直觀的Web界面體驗(yàn)。

技術(shù)架構(gòu)解析

后端核心設(shè)計(jì)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.web.bind.annotation.*;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.*;

/**
 * @Author:Derek_Smart
 * @Date:2025/8/4 17:17
 */
@RestController
@RequestMapping("/sqlUtil")
public class SqlUtilResource {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private final Logger logger = LoggerFactory.getLogger(SqlUtilResource.class);

    @PostMapping("/executeSqlp")
    public Map<String, Object> executeSqlp(@RequestBody SqlRequest request) {
        try {
            String sql = request.getSql();
            List<Object> params = request.getParams();

            if (sql.trim().toUpperCase().startsWith("SELECT")) {
                // 查詢操作
                List<Map<String, Object>> result = jdbcTemplate.query(sql, params.toArray(), (rs, rowNum) -> {
                    Map<String, Object> row = new LinkedHashMap<>();
                    ResultSetMetaData metaData = rs.getMetaData();
                    int columnCount = metaData.getColumnCount();
                    for (int i = 1; i <= columnCount; i++) {
                        String columnName = metaData.getColumnLabel(i);
                        row.put(columnName, rs.getObject(i));
                    }
                    return row;
                });

                return createSuccessResponse(result);
            } else {
                // 更新操作
                int affectedRows = jdbcTemplate.update(sql, params.toArray());
                return createSuccessResponse(Collections.singletonMap("affectedRows", affectedRows));
            }
        } catch (Exception e) {
            logger.error("SQL執(zhí)行錯(cuò)誤: {}", e.getMessage(), e);
            return createErrorResponse("SQL執(zhí)行錯(cuò)誤: " + e.getMessage());
        }
    }


    @PostMapping("/executeSql")
    public Map<String, Object> executeSql(@RequestBody SqlRequest request) {
        try {
            String sql = request.getSql();
            List<Object> params = request.getParams();

            if (sql.trim().toUpperCase().startsWith("SELECT")) {
                // 查詢操作 - 返回列和行數(shù)據(jù)
                return handleQuery(sql, params);
            } else {
                // 更新操作 - 返回受影響行數(shù)
                return handleUpdate(sql, params);
            }
        } catch (Exception e) {
            logger.error("SQL執(zhí)行錯(cuò)誤: {}", e.getMessage(), e);
            return createErrorResponse("SQL執(zhí)行錯(cuò)誤: " + e.getMessage());
        }
    }


    /**
     * 獲取數(shù)據(jù)庫所有表名
     * @return 表名列表
     */
    @GetMapping("/getTables")
    public Map<String, Object> getDatabaseTables() {
        try {
            return jdbcTemplate.execute((ConnectionCallback<Map<String, Object>>) connection -> {
                DatabaseMetaData metaData = connection.getMetaData();
                // 獲取所有表名(不包含系統(tǒng)表)
                ResultSet tables = metaData.getTables(
                        null, null, null, new String[]{"TABLE"});

                List<String> tableNames = new ArrayList<>();
                while (tables.next()) {
                    String tableName = tables.getString("TABLE_NAME");
                    tableNames.add(tableName);
                }

                // 按字母順序排序
                Collections.sort(tableNames);

                // 構(gòu)建響應(yīng)
                Map<String, Object> response = new LinkedHashMap<>();
                response.put("success", true);
                response.put("data", tableNames);
                return response;
            });
        } catch (Exception e) {
            logger.error("獲取數(shù)據(jù)庫表失敗: {}", e.getMessage(), e);
            Map<String, Object> errorResponse = new LinkedHashMap<>();
            errorResponse.put("success", false);
            errorResponse.put("message", "獲取數(shù)據(jù)庫表失敗: " + e.getMessage());
            return errorResponse;
        }
    }


    private Map<String, Object> handleQuery(String sql, List<Object> params) {
        try {
            return jdbcTemplate.query(sql, params.toArray(), (ResultSetExtractor<Map<String, Object>>) rs -> {
                ResultSetMetaData metaData = rs.getMetaData();
                int columnCount = metaData.getColumnCount();

                // 獲取列名
                List<String> columns = new ArrayList<>();
                for (int i = 1; i <= columnCount; i++) {
                    columns.add(metaData.getColumnLabel(i));
                }

                // 獲取行數(shù)據(jù)
                List<List<Object>> rows = new ArrayList<>();
                while (rs.next()) {
                    List<Object> row = new ArrayList<>();
                    for (int i = 1; i <= columnCount; i++) {
                        row.add(rs.getObject(i));
                    }
                    rows.add(row);
                }

                // 構(gòu)建響應(yīng)
                Map<String, Object> data = new LinkedHashMap<>();
                data.put("columns", columns);
                data.put("rows", rows);

                Map<String, Object> response = new LinkedHashMap<>();
                response.put("success", true);
                response.put("data", data);
                return response;
            });
        } catch (Exception e) {
            logger.error("查詢處理失敗: {}", e.getMessage(), e);
            return createErrorResponse("查詢處理失敗: " + e.getMessage());
        }
    }




    private Map<String, Object> handleUpdate(String sql, List<Object> params) {
        int affectedRows = jdbcTemplate.update(sql, params.toArray());

        Map<String, Object> data = new LinkedHashMap<>();
        data.put("affectedRows", affectedRows);

        Map<String, Object> response = new LinkedHashMap<>();
        response.put("success", true);
        response.put("data", data);
        return response;
    }

    // 創(chuàng)建成功響應(yīng)
    private Map<String, Object> createSuccessResponse(Object data) {
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("success", true);
        response.put("timestamp", System.currentTimeMillis());
        response.put("data", data);
        return response;
    }

    // 創(chuàng)建錯(cuò)誤響應(yīng)
    private Map<String, Object> createErrorResponse(String message) {
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("success", false);
        response.put("timestamp", System.currentTimeMillis());
        response.put("message", message);
        return response;
    }

    public static class SqlRequest {
        private String sql;
        private List<Object> params = new ArrayList<>();

        // Getters and Setters
        public String getSql() {
            return sql;
        }

        public void setSql(String sql) {
            this.sql = sql;
        }

        public List<Object> getParams() {
            return params;
        }

        public void setParams(List<Object> params) {
            this.params = params;
        }
    }
}

后端采用策略模式實(shí)現(xiàn)SQL路由,自動(dòng)區(qū)分查詢與更新操作。通過JDBC元數(shù)據(jù)接口實(shí)現(xiàn)數(shù)據(jù)庫自發(fā)現(xiàn)能力,為前端提供結(jié)構(gòu)化數(shù)據(jù)支撐。

前端創(chuàng)新交互

<template>
  <!-- 雙面板設(shè)計(jì) -->
  <div class="editor-container">
    <!-- 元數(shù)據(jù)導(dǎo)航區(qū) -->
    <div class="sidebar">
      <div v-for="table in tables" @click="selectTable(table)">
        <i class="fas fa-table"></i> {{ table }}
      </div>
    </div>
    
    <!-- SQL工作區(qū) -->
    <textarea 
      v-model="sqlQuery"
      @keydown.ctrl.enter="executeSql"></textarea>
    
    <!-- 智能結(jié)果渲染 -->
    <el-table :data="result.data.rows">
      <el-table-column 
        v-for="(column, index) in result.data.columns"
        :label="column">
        <template v-slot="scope">
          <span v-if="scope.row[index] === null" class="null-value">NULL</span>
          <span v-else-if="typeof scope.row[index] === 'object'" 
                @click="showJsonDetail(scope.row[index])">
            {{ jsonPreview(scope.row[index]) }}
          </span>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

前端實(shí)現(xiàn)三區(qū)域布局:元數(shù)據(jù)導(dǎo)航、SQL編輯器和智能結(jié)果面板。采用動(dòng)態(tài)類型檢測技術(shù),對(duì)NULL值、JSON對(duì)象等特殊數(shù)據(jù)類型進(jìn)行可視化區(qū)分處理。

核心技術(shù)亮點(diǎn)

實(shí)時(shí)元數(shù)據(jù)發(fā)現(xiàn)

  • 自動(dòng)加載數(shù)據(jù)庫表結(jié)構(gòu)
  • 表名智能排序與即時(shí)搜索
  • 點(diǎn)擊表名自動(dòng)生成SELECT模板

智能SQL處理

// SQL執(zhí)行核心邏輯
async executeSql() {
  const paginatedSql = this.addPagination(this.sqlQuery);
  const response = await executeSql(paginatedSql);
  
  // 高級(jí)結(jié)果處理
  if (response.success) {
    this.result = {
      ...response,
      data: {
        ...response.data,
        executionTime: Date.now() - startTime
      }
    }
  }
}
  • 自動(dòng)分頁機(jī)制
  • 執(zhí)行耗時(shí)精準(zhǔn)統(tǒng)計(jì)
  • 語法錯(cuò)誤實(shí)時(shí)反饋

歷史版本管理

// 歷史記錄管理
addToHistory(sql) {
  if (this.history.includes(sql)) return;
  this.history.unshift(sql);
  localStorage.setItem('sqlHistory', JSON.stringify(this.history));
}

本地存儲(chǔ)自動(dòng)持久化

智能去重機(jī)制

一鍵恢復(fù)歷史查詢

數(shù)據(jù)可視化增強(qiáng)

  • JSON對(duì)象折疊預(yù)覽
  • NULL值特殊標(biāo)識(shí)
  • 分頁控件動(dòng)態(tài)加載

性能優(yōu)化策略

查詢優(yōu)化

  • 自動(dòng)追加LIMIT子句
  • 分頁查詢按需加載
  • 結(jié)果集流式處理

緩存機(jī)制

// 表結(jié)構(gòu)緩存
async fetchDatabaseTables() {
  if (this.cachedTables) return this.cachedTables;
  const response = await fetchTables();
  this.cachedTables = response.data;
}

完整vue代碼:

<template>
  <div class="sql-executor-container">
    <!-- 頭部 -->
    <header>
      <div class="logo">
        <i class="fas fa-database logo-icon"></i>
        <div>
          <h1>SQL 執(zhí)行工具</h1>
          <p>網(wǎng)頁版數(shù)據(jù)庫客戶端工具</p>
        </div>
      </div>
      <div class="connection-info">
        <i class="fas fa-plug"></i> 已連接到 {{ connectionName }} 數(shù)據(jù)庫
      </div>
    </header>

    <!-- 主體內(nèi)容 -->
    <div class="main-content">
      <!-- 側(cè)邊欄 -->
      <div class="sidebar">
        <div class="sidebar-section">
          <h3>數(shù)據(jù)庫表</h3>
          <div
              v-for="table in tables"
              :key="table"
              class="schema-item"
              :class="{ active: selectedTable === table }"
              @click="selectTable(table)"
          >
            <i class="fas fa-table"></i> {{ table }}
          </div>
        </div>

        <div class="sidebar-section">
          <h3>歷史查詢</h3>
          <div
              v-for="(query, index) in history"
              :key="index"
              class="schema-item history-item"
              @click="loadHistoryQuery(query)"
          >
            <i class="fas fa-history"></i> {{ query.substring(0, 40) }}{{ query.length > 40 ? '...' : '' }}
          </div>
        </div>
      </div>

      <!-- 編輯器區(qū)域 -->
      <div class="editor-container">
        <div class="sql-editor-container">
          <div class="sql-editor-header">
            <h2>SQL 編輯器</h2>
            <div class="toolbar">
              <el-button
                  class="toolbar-btn run"
                  @click="executeSql"
                  :loading="loading"
              >
                <i class="fas fa-play"></i> 執(zhí)行
              </el-button>
              <el-button
                  class="toolbar-btn format"
                  @click="formatSql"
              >
                <i class="fas fa-indent"></i> 格式化
              </el-button>
              <el-button
                  class="toolbar-btn clear"
                  @click="clearEditor"
              >
                <i class="fas fa-trash-alt"></i> 清除
              </el-button>
            </div>
          </div>
          <textarea
              class="sql-editor"
              v-model="sqlQuery"
              placeholder="輸入 SQL 語句,例如:SELECT * FROM table_name"
              @keydown.ctrl.enter="executeSql"
          ></textarea>
        </div>

        <!-- 結(jié)果區(qū)域 -->
        <div class="result-container">
          <div class="result-header">
            <h2>執(zhí)行結(jié)果</h2>
            <div class="result-info">
              <div
                  class="result-status"
                  :class="result.success ? 'status-success' : 'status-error'"
                  v-if="result"
              >
                {{ result.success ? '執(zhí)行成功' : '執(zhí)行失敗' }}
              </div>
              <div class="rows-count" v-if="result && result.success">
                共 {{ result.data.total }} 行數(shù)據(jù) (顯示 {{ result.data.rows.length }} 行)
              </div>
              <div class="execution-time" v-if="result && result.success">
                <i class="fas fa-clock"></i> {{ result.data.executionTime }} ms
              </div>
            </div>
          </div>

          <div class="result-content">
            <!-- 加載狀態(tài) -->
            <div v-if="loading" class="no-data">
              <i class="el-icon-loading" style="font-size: 48px;"></i>
              <p>正在執(zhí)行查詢...</p>
            </div>

            <!-- 查詢結(jié)果 -->
            <div v-else-if="result && result.success && result.data.rows.length > 0" class="table-container">
              <div class="table-wrapper">
                <el-table
                    :data="result.data.rows"
                    height="100%"
                    style="width: 100%"
                    stripe
                    border
                    size="small"
                    :default-sort = "{prop: 'id', order: 'ascending'}"
                >
                  <el-table-column
                      v-for="(column, index) in result.data.columns"
                      :key="index"
                      :prop="'row.' + index"
                      :label="column"
                      min-width="150"
                  >
<!--                    <template slot-scope="scope">
                      <span v-if="scope.row[column] === null" class="null-value">NULL</span>
                      <span v-else-if="typeof scope.row[column] === 'object'" class="json-value" @click="showJsonDetail(scope.row[column])">
                        {{ jsonPreview(scope.row[column]) }}
                      </span>
                      <span v-else>{{ scope.row[column] }}</span>
                    </template>-->

                    <template slot-scope="scope">
                      <span v-if="scope.row[index] === null" class="null-value">NULL</span>
                      <span v-else-if="typeof scope.row[index] === 'object'" class="json-value" @click="showJsonDetail(scope.row[index])">
                      {{ jsonPreview(scope.row[index]) }}
                    </span>
                      <span v-else>{{ scope.row[index] }}</span>
                    </template>

                  </el-table-column>
                </el-table>
              </div>

              <!-- 分頁控件 -->
              <el-pagination
                  v-if="result.data.total > pageSize"
                  @size-change="handleSizeChange"
                  @current-change="handleCurrentChange"
                  :current-page="currentPage"
                  :page-sizes="[10, 20, 50, 100]"
                  :page-size="pageSize"
                  layout="total, sizes, prev, pager, next, jumper"
                  :total="result.data.total"
                  class="pagination"
              >
              </el-pagination>
            </div>

            <!-- 空結(jié)果 -->
            <div v-else-if="result && result.success && result.data.rows.length === 0" class="no-data">
              <i class="el-icon-inbox" style="font-size: 48px;"></i>
              <p>未查詢到數(shù)據(jù)</p>
            </div>

            <!-- 錯(cuò)誤信息 -->
            <div v-else-if="result && !result.success" class="error-container">
              <div class="error-header">
                <i class="el-icon-warning-outline" style="font-size: 20px;"></i>
                <h3>SQL 執(zhí)行錯(cuò)誤</h3>
              </div>
              <div class="error-details">
                {{ result.message }}
              </div>
            </div>

            <!-- 初始狀態(tài) -->
            <div v-else class="no-data">
              <i class="el-icon-document" style="font-size: 48px;"></i>
              <p>輸入 SQL 并執(zhí)行以查看結(jié)果</p>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 頁腳 -->
    <div class="footer">
      <div class="copyright">
        ? {{ new Date().getFullYear() }} SQL執(zhí)行工具 - 網(wǎng)頁版數(shù)據(jù)庫客戶端
      </div>
      <div class="shortcuts">
        <div class="shortcut-item">
          <span class="key">Ctrl</span> + <span class="key">Enter</span> 執(zhí)行查詢
        </div>
        <div class="shortcut-item">
          <span class="key">Ctrl</span> + <span class="key">/</span> 格式化 SQL
        </div>
      </div>
    </div>

    <!-- JSON 詳情彈窗 -->
    <el-dialog
        title="JSON 詳情"
        :visible.sync="showJsonModal"
        width="70%"
        top="5vh"
    >
      <pre class="json-content">{{ formatJson(currentJson) }}</pre>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="showJsonModal = false">關(guān) 閉</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { fetchTables, executeSql } from '@/api/sqlUtil/sqlUtil';

export default {
  name: 'SqlUtil',
  data() {
    return {
      sqlQuery: '',
      result: null,
      loading: false,
      tables: [],
      selectedTable: null,
      history: [],
      showJsonModal: false,
      currentJson: null,
      connectionName: '生產(chǎn)',
      currentPage: 1,
      pageSize: 20
    }
  },
  mounted() {
    // 從本地存儲(chǔ)加載歷史記錄
    const savedHistory = localStorage.getItem('sqlHistory');
    if (savedHistory) {
      this.history = JSON.parse(savedHistory);
    }

    // 獲取當(dāng)前連接信息
    this.getConnectionInfo();

    // 獲取數(shù)據(jù)庫表
    this.fetchDatabaseTables();
  },
  methods: {
    async fetchDatabaseTables() {
      try {
        this.loading = true;
        const response = await fetchTables();
        this.tables = response.data || [];
        if (this.tables.length > 0) {
          this.selectedTable = this.tables[0];
          this.sqlQuery = `SELECT * FROM ${this.selectedTable}`;
        }
      } catch (error) {
        console.error('獲取數(shù)據(jù)庫表失敗:', error);
        this.$message.error('獲取數(shù)據(jù)庫表失敗: ' + error.message);
      } finally {
        this.loading = false;
      }
    },

    async executeSql() {
      if (!this.sqlQuery.trim()) {
        this.result = {
          success: false,
          message: 'SQL 語句不能為空'
        };
        return;
      }

      this.loading = true;
      this.result = null;

      try {
        // 添加分頁參數(shù)
        const paginatedSql = this.addPagination(this.sqlQuery);
        const response = await executeSql(paginatedSql);
        this.result = response;

        // 保存到歷史記錄
        this.addToHistory(this.sqlQuery);
      } catch (error) {
        this.result = {
          success: false,
          message: `請(qǐng)求失敗: ${error.message || error}`
        };
      } finally {
        this.loading = false;
      }
    },

    // 添加分頁到SQL
    addPagination(sql) {
      // 如果是SELECT查詢,添加分頁
      /*if (sql.trim().toUpperCase().startsWith('SELECT')) {
        const offset = (this.currentPage - 1) * this.pageSize;
        return `${sql} LIMIT ${offset}, ${this.pageSize}`;
      }*/
      return sql;
    },

    handleSizeChange(val) {
      this.pageSize = val;
      this.currentPage = 1;
      if (this.sqlQuery.trim()) {
        this.executeSql();
      }
    },

    handleCurrentChange(val) {
      this.currentPage = val;
      if (this.sqlQuery.trim()) {
        this.executeSql();
      }
    },

    // 獲取數(shù)據(jù)庫連接信息
    getConnectionInfo() {
      // 這里可以根據(jù)實(shí)際情況從API獲取或從配置讀取
      const env = process.env.NODE_ENV;
      this.connectionName = env === 'production' ? '生產(chǎn)' :
          env === 'staging' ? '預(yù)發(fā)布' : '開發(fā)';
    },

    formatSql() {
      // 簡單的 SQL 格式化
      this.sqlQuery = this.sqlQuery
          .replace(/\b(SELECT|FROM|WHERE|AND|OR|ORDER BY|GROUP BY|HAVING|INSERT|UPDATE|DELETE|JOIN|INNER JOIN|LEFT JOIN|RIGHT JOIN|ON|AS|LIMIT)\b/gi, '\n$1')
          .replace(/,/g, ',\n')
          .replace(/;/g, ';\n')
          .replace(/\n\s+\n/g, '\n')
          .trim();
    },

    clearEditor() {
      this.sqlQuery = '';
      this.result = null;
    },

    selectTable(table) {
      this.selectedTable = table;
      this.sqlQuery = `SELECT * FROM ${table} LIMIT 100`;
    },

    addToHistory(sql) {
      // 避免重復(fù)添加
      if (this.history.includes(sql)) return;

      this.history.unshift(sql);

      // 限制歷史記錄數(shù)量
      if (this.history.length > 10) {
        this.history.pop();
      }

      // 保存到本地存儲(chǔ)
      localStorage.setItem('sqlHistory', JSON.stringify(this.history));
    },

    loadHistoryQuery(sql) {
      this.sqlQuery = sql;
    },

    jsonPreview(obj) {
      try {
        const str = JSON.stringify(obj);
        return str.length > 50 ? str.substring(0, 47) + '...' : str;
      } catch {
        return '[Object]';
      }
    },

    showJsonDetail(obj) {
      this.currentJson = obj;
      this.showJsonModal = true;
    },

    formatJson(obj) {
      return JSON.stringify(obj, null, 2);
    }
  }
}
</script>

<style scoped>
.sql-executor-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  max-width: 100%;
  margin: 0 auto;
  background-color: rgba(255, 255, 255, 0.95);
  overflow: hidden;
}

header {
  background: linear-gradient(90deg, #2c3e50, #4a6491);
  color: white;
  padding: 15px 30px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0;
}

.logo {
  display: flex;
  align-items: center;
  gap: 15px;
}

.logo-icon {
  font-size: 28px;
  color: #42b983;
}

.logo h1 {
  font-weight: 600;
  font-size: 22px;
}

.logo p {
  opacity: 0.8;
  font-size: 13px;
  margin-top: 4px;
}

.connection-info {
  background: rgba(255, 255, 255, 0.1);
  padding: 8px 15px;
  border-radius: 20px;
  font-size: 13px;
}

.main-content {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.sidebar {
  width: 260px;
  background: #f8f9fa;
  border-right: 1px solid #eaeaea;
  padding: 15px 0;
  overflow-y: auto;
  flex-shrink: 0;
}

.sidebar-section {
  margin-bottom: 20px;
}

.sidebar-section h3 {
  padding: 0 20px 10px;
  font-size: 16px;
  color: #4a5568;
  border-bottom: 1px solid #eaeaea;
  margin-bottom: 12px;
}

.schema-item {
  padding: 8px 20px 8px 35px;
  cursor: pointer;
  transition: all 0.2s;
  position: relative;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.schema-item i {
  font-size: 14px;
  width: 20px;
}

.schema-item:hover {
  background: #e9ecef;
}

.schema-item.active {
  background: #e3f2fd;
  color: #1a73e8;
  font-weight: 500;
}

.history-item {
  font-size: 13px;
  color: #666;
}

.editor-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

.sql-editor-container {
  flex: 0 0 40%;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid #eaeaea;
  min-height: 200px;
  overflow: hidden;
}

.sql-editor-header {
  padding: 12px 20px;
  background: #f1f3f4;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #eaeaea;
  flex-shrink: 0;
}

.sql-editor-header h2 {
  font-size: 17px;
  color: #2d3748;
}

.toolbar {
  display: flex;
  gap: 8px;
}

.toolbar-btn {
  padding: 7px 12px;
  border: none;
  border-radius: 6px;
  background: #4a6491;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  transition: all 0.2s;
}

.toolbar-btn:hover {
  opacity: 0.9;
  transform: translateY(-1px);
}

.toolbar-btn i {
  font-size: 13px;
}

.toolbar-btn.run {
  background: #42b983;
}

.toolbar-btn.format {
  background: #f0ad4e;
}

.toolbar-btn.clear {
  background: #e74c3c;
}

.sql-editor {
  flex: 1;
  padding: 15px;
  font-family: 'Fira Code', 'Consolas', monospace;
  font-size: 14px;
  line-height: 1.5;
  border: none;
  resize: none;
  outline: none;
  background: #fcfcfc;
  min-height: 100px;
  overflow: auto;
}

.result-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.result-header {
  padding: 12px 20px;
  background: #f1f3f4;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #eaeaea;
  flex-shrink: 0;
}

.result-header h2 {
  font-size: 17px;
  color: #2d3748;
}

.result-info {
  display: flex;
  align-items: center;
  gap: 15px;
  font-size: 13px;
}

.result-status {
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 500;
}

.status-success {
  background: #e8f5e9;
  color: #2e7d32;
}

.status-error {
  background: #ffebee;
  color: #c62828;
}

.rows-count, .execution-time {
  color: #5f6368;
  display: flex;
  align-items: center;
  gap: 4px;
}

.result-content {
  flex: 1;
  overflow: auto;
  position: relative;
  min-height: 0;
}

.table-container {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.table-wrapper {
  flex: 1;
  overflow: auto;
  min-height: 200px;
}

.el-table {
  width: 100%;
  min-width: 1000px;
}

.pagination {
  padding: 10px 15px;
  border-top: 1px solid #ebeef5;
  background: #fff;
  display: flex;
  justify-content: flex-end;
}

.null-value {
  color: #b0b0b0;
  font-style: italic;
}

.json-value {
  color: #d35400;
  cursor: pointer;
  text-decoration: underline dotted;
}

.no-data {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #909399;
  font-size: 14px;
}

.error-container {
  padding: 20px;
  background-color: #ffebee;
  border-radius: 8px;
  margin: 20px;
  color: #c62828;
}

.error-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.error-details {
  font-family: monospace;
  font-size: 14px;
  white-space: pre-wrap;
  background: rgba(255, 255, 255, 0.7);
  padding: 15px;
  border-radius: 6px;
  margin-top: 10px;
  overflow-x: auto;
}

.footer {
  padding: 12px 30px;
  background: #f8f9fa;
  border-top: 1px solid #eaeaea;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  color: #6c757d;
  flex-shrink: 0;
}

.shortcuts {
  display: flex;
  gap: 15px;
}

.shortcut-item {
  display: flex;
  align-items: center;
  gap: 5px;
}

.key {
  background: #e9ecef;
  padding: 3px 8px;
  border-radius: 4px;
  font-weight: 500;
  font-size: 11px;
}

.json-content {
  background: #f8f8f8;
  padding: 15px;
  border-radius: 4px;
  max-height: 70vh;
  overflow: auto;
  font-family: monospace;
  line-height: 1.5;
  font-size: 14px;
}

@media (max-width: 992px) {
  .main-content {
    flex-direction: column;
  }

  .sidebar {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid #eaeaea;
    height: 200px;
    overflow: auto;
  }

  .result-info {
    flex-wrap: wrap;
    gap: 8px;
  }
}

@media (max-width: 768px) {
  header {
    flex-direction: column;
    gap: 10px;
    text-align: center;
  }

  .logo {
    flex-direction: column;
    gap: 5px;
  }

  .connection-info {
    margin-top: 5px;
  }

  .toolbar {
    flex-wrap: wrap;
    justify-content: center;
  }

  .footer {
    flex-direction: column;
    gap: 10px;
  }
}
</style>
import request from '@/utils/request'


export function executeSql(sql) {
    return request({
        url: '/sqlUtil/executeSql',
        method: 'post',
        data: { sql: sql }
    })
}


export function fetchTables() {
    return request({
        url: '/sqlUtil/getTables',
        method: 'get'
    })
}

import {Message} from 'element-ui'
import axios from 'axios'

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'
import store from "@/store";
import router from '@/router';
// create an axios instance
const service = axios.create({
    baseURL: `http://127.0.0.1:8080`,
    timeout: 75000,
})

export default service

效果:

總結(jié)

該SQL執(zhí)行工具通過四大創(chuàng)新設(shè)計(jì)重塑了數(shù)據(jù)庫交互體驗(yàn):

  • 元數(shù)據(jù)驅(qū)動(dòng):將數(shù)據(jù)庫結(jié)構(gòu)轉(zhuǎn)化為可視化導(dǎo)航
  • 上下文感知:自動(dòng)識(shí)別SQL類型并優(yōu)化執(zhí)行
  • 漸進(jìn)式渲染:平衡大數(shù)據(jù)量與用戶體驗(yàn)
  • 歷史時(shí)空隧道:完整記錄操作軌跡

到此這篇關(guān)于基于SpringBoot與Vue開發(fā)Web版SQL執(zhí)行工具的文章就介紹到這了,更多相關(guān)SpringBoot開發(fā)SQL執(zhí)行工具內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java字節(jié)碼ByteBuddy使用及原理解析上

    Java字節(jié)碼ByteBuddy使用及原理解析上

    這篇文章主要為大家介紹了Java字節(jié)碼ByteBuddy使用及原理解析上篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • java簡易小游戲制作代碼

    java簡易小游戲制作代碼

    這篇文章主要介紹了java簡易小游戲制作代碼,代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Java反射機(jī)制詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java反射機(jī)制詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java 反射機(jī)制。通俗來講呢,就是在運(yùn)行狀態(tài)中,我們可以根據(jù)“類的部分已經(jīng)的信息”來還原“類的全部的信息”。這篇文章給大家詳細(xì)介紹了java反射機(jī)制的知識(shí),感興趣的朋友一起看看吧
    2017-06-06
  • Javaweb中使用Servlet編寫簡單的接口案例詳解

    Javaweb中使用Servlet編寫簡單的接口案例詳解

    文章介紹了如何使用Servlet編寫一個(gè)簡單的接口來校驗(yàn)用戶提交的密碼長度是否在6到12位之間,代碼分為后端部分和前端部分,給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-02-02
  • Java中CaffeineCache自定義緩存時(shí)間的實(shí)現(xiàn)

    Java中CaffeineCache自定義緩存時(shí)間的實(shí)現(xiàn)

    本文主要介紹了Java中CaffeineCache自定義緩存時(shí)間的實(shí)現(xiàn),通過聲明緩存value值holder對(duì)象并創(chuàng)建緩存容器,可以為不同的key值指定不同的過期時(shí)間,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-02-02
  • Spring集成Quartz的簡單配置的方法

    Spring集成Quartz的簡單配置的方法

    本篇文章主要介紹了Spring集成Quartz的簡單配置的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • 聊聊SpringBoot使用Nacos進(jìn)行服務(wù)注冊發(fā)現(xiàn)與配置管理問題

    聊聊SpringBoot使用Nacos進(jìn)行服務(wù)注冊發(fā)現(xiàn)與配置管理問題

    Nacos支持基于DNS和基于RPC的服務(wù)發(fā)現(xiàn)(可以作為springcloud的注冊中心)、動(dòng)態(tài)配置服務(wù)(可以做配置中心)、動(dòng)態(tài)?DNS?服務(wù)。本文重點(diǎn)給大家介紹SpringBoot使用Nacos進(jìn)行服務(wù)注冊發(fā)現(xiàn)與配置管理,感興趣的朋友一起看看吧
    2022-01-01
  • SpringBoot項(xiàng)目導(dǎo)出jar包及瘦身部署方式

    SpringBoot項(xiàng)目導(dǎo)出jar包及瘦身部署方式

    今天項(xiàng)目要求Nginx+jar包運(yùn)行多個(gè)項(xiàng)目,在此記錄一下部署的過程,其中借鑒了好多網(wǎng)上前輩的經(jīng)驗(yàn),感謝各位的無私分享
    2024-07-07
  • SpringCloud OpenFeign概述與使用

    SpringCloud OpenFeign概述與使用

    OpenFeign源于Netflix的Feign,是http通信的客戶端。屏蔽了網(wǎng)絡(luò)通信的細(xì)節(jié),直接面向接口的方式開發(fā),讓開發(fā)者感知不到網(wǎng)絡(luò)通信細(xì)節(jié)。所有遠(yuǎn)程調(diào)用,都像調(diào)用本地方法一樣完成
    2023-01-01
  • SpringBoot接入釘釘自定義機(jī)器人預(yù)警通知

    SpringBoot接入釘釘自定義機(jī)器人預(yù)警通知

    本文主要介紹了SpringBoot接入釘釘自定義機(jī)器人預(yù)警通知,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評(píng)論

郁南县| 邯郸市| 三明市| 新安县| 吐鲁番市| 阿图什市| 石嘴山市| 兴宁市| 若尔盖县| 阿合奇县| 四川省| 大石桥市| 长乐市| 耒阳市| 铜陵市| 巴林右旗| 清河县| 玉龙| 都江堰市| 清水河县| 鄱阳县| 泸西县| 安丘市| 轮台县| 原阳县| 石嘴山市| 油尖旺区| 蓬安县| 奈曼旗| 昔阳县| 合山市| 吉隆县| 门头沟区| 云梦县| 泾源县| 聊城市| 石渠县| 桐乡市| 公安县| 合山市| 天津市|