|
|
@@ -0,0 +1,750 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <el-button type="text" size="small" @click="goBack">
|
|
|
+ <i class="el-icon-back">返回上一页</i>
|
|
|
+ </el-button>
|
|
|
+ <div class="container">
|
|
|
+ <el-card class="card" shadow="never">
|
|
|
+ <div class="left-content">
|
|
|
+ <div class="action-buttons">
|
|
|
+ <el-button type="primary" @click="openConfigDialog">修改配置文件</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="change-list-container">
|
|
|
+ <el-divider content-position="left">
|
|
|
+ 更改历史
|
|
|
+ </el-divider>
|
|
|
+ <div v-if="changeList.length" class="change-list">
|
|
|
+ <div v-for="(item, index) in changeList" :key="index" class="change-item" @click="viewChangeDetail(item, index)">
|
|
|
+ <div class="change-content">
|
|
|
+ {{ item.description }}
|
|
|
+ </div>
|
|
|
+ <div class="change-time">
|
|
|
+ {{ item.time }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="no-changes">
|
|
|
+ <el-empty description="当前暂无更改操作">
|
|
|
+ <template #description>
|
|
|
+ <p v-if="loading">正在加载数据...</p>
|
|
|
+ <p v-else>当前暂无更改操作</p>
|
|
|
+ </template>
|
|
|
+ </el-empty>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-divider direction="vertical" class="divider"></el-divider>
|
|
|
+
|
|
|
+ <el-card class="card" shadow="never">
|
|
|
+ <div class="right-content">
|
|
|
+ <div class="action-buttons">
|
|
|
+ <el-button type="primary" @click="analyzeProject">项目分析</el-button>
|
|
|
+ <el-button type="primary" @click="askQuestion">选中项目文件提问</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="conversation-container">
|
|
|
+ <el-divider content-position="left">
|
|
|
+ 对话历史
|
|
|
+ </el-divider>
|
|
|
+ <div v-if="conversations.length" class="conversation-list">
|
|
|
+ <div v-for="(item, index) in conversations"
|
|
|
+ :key="index"
|
|
|
+ class="conversation-item"
|
|
|
+ @click="viewConversationDetail(item, index)">
|
|
|
+ <div class="conversation-header">
|
|
|
+ <span class="conversation-type">{{ item.type }}</span>
|
|
|
+ <span class="conversation-time">{{ item.time }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="conversation-content">
|
|
|
+ {{ item.content }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="no-conversations">
|
|
|
+ <el-empty description="暂无对话历史"></el-empty>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置文件修改对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="修改配置文件"
|
|
|
+ v-model="dialogVisible"
|
|
|
+ width="50%">
|
|
|
+ <div class="dialog-content">
|
|
|
+ <el-form :model="configForm" label-width="120px" ref="configFormRef">
|
|
|
+ <el-form-item label="文件路径" prop="filePath" :rules="[{ required: true, message: '请输入文件路径', trigger: 'blur' }]">
|
|
|
+ <el-input v-model="configForm.filePath" placeholder="请输入文件路径"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="修改方式" prop="modifyMethod" :rules="[{ required: true, message: '请选择修改方式', trigger: 'change' }]">
|
|
|
+ <el-select v-model="configForm.modifyMethod" placeholder="请选择修改方式" style="width: 100%">
|
|
|
+ <el-option label="AST方式修改" value="ast"></el-option>
|
|
|
+ <el-option label="大模型(LLM)方式修改" value="llm"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="closeConfigDialog">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="saveConfig">确认</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 项目分析对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="项目分析"
|
|
|
+ v-model="analysisDialogVisible"
|
|
|
+ width="60%"
|
|
|
+ :fullscreen="showAnalysisResult">
|
|
|
+ <div class="dialog-content" v-if="!showAnalysisResult">
|
|
|
+ <el-form :model="analysisForm" label-width="120px" ref="analysisFormRef">
|
|
|
+ <el-form-item label="项目路径" prop="projectPath" :rules="[{ required: true, message: '请输入项目路径', trigger: 'blur' }]">
|
|
|
+ <el-input v-model="analysisForm.projectPath" placeholder="请输入项目路径,例如: E:/codes/MyProject"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ <div v-else class="analysis-result">
|
|
|
+ <div v-if="analysisLoading" class="analysis-loading">
|
|
|
+ <el-spinner></el-spinner>
|
|
|
+ <p>正在分析项目,请稍候...</p>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <div v-html="formattedAnalysisResult" class="markdown-content"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="closeAnalysisDialog">{{ showAnalysisResult ? '关闭' : '取消' }}</el-button>
|
|
|
+ <el-button v-if="!showAnalysisResult" type="primary" :loading="analysisLoading" @click="submitProjectAnalysis">开始分析</el-button>
|
|
|
+ <el-button v-else type="primary" @click="saveAnalysis">保存分析结果</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 文件提问对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="选中项目文件提问"
|
|
|
+ v-model="questionDialogVisible"
|
|
|
+ width="50%">
|
|
|
+ <div class="dialog-content">
|
|
|
+ <div class="question-form">
|
|
|
+ <el-form label-position="top">
|
|
|
+ <el-form-item label="选择文件路径">
|
|
|
+ <el-input
|
|
|
+ v-model="selectedFile"
|
|
|
+ placeholder="请输入或选择文件的完整路径,例如: E:/project/src/main.js"
|
|
|
+ clearable>
|
|
|
+ </el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="提问内容">
|
|
|
+ <el-input
|
|
|
+ type="textarea"
|
|
|
+ v-model="questionContent"
|
|
|
+ rows="4"
|
|
|
+ placeholder="请输入您的问题,例如:这个文件的主要功能是什么?"
|
|
|
+ :maxlength="500"
|
|
|
+ show-word-limit>
|
|
|
+ </el-input>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="questionDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="submitQuestion">提交问题</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { CHANGE_DETAIL, CONVERSATION_DETAIL } from '@/router/name';
|
|
|
+import axios from 'axios';
|
|
|
+import marked from 'marked';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ConfigPatch.vue',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ changeList: [],
|
|
|
+ dialogVisible: false,
|
|
|
+ analysisDialogVisible: false,
|
|
|
+ questionDialogVisible: false,
|
|
|
+ selectedFile: '',
|
|
|
+ questionContent: '',
|
|
|
+ conversations: [],
|
|
|
+ conversationsLoading: false,
|
|
|
+ loading: false,
|
|
|
+ configForm: {
|
|
|
+ filePath: '',
|
|
|
+ modifyMethod: '',
|
|
|
+ },
|
|
|
+ submitting: false,
|
|
|
+ showAnalysisResult: false,
|
|
|
+ analysisLoading: false,
|
|
|
+ analysisForm: {
|
|
|
+ projectPath: '',
|
|
|
+ },
|
|
|
+ formattedAnalysisResult: '',
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.fetchChangeList();
|
|
|
+ this.fetchConversations();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ goBack() {
|
|
|
+ this.$router.go(-1);
|
|
|
+ this.loadCommitInfo();
|
|
|
+ },
|
|
|
+ fetchChangeList() {
|
|
|
+ this.loading = true;
|
|
|
+ axios.get('http://localhost:8080/config/changes')
|
|
|
+ .then((response) => {
|
|
|
+ if (response.data && response.data.length) {
|
|
|
+ // Transform the API response to match our UI format
|
|
|
+ this.changeList = response.data.map((item) => ({
|
|
|
+ id: item.id,
|
|
|
+ description: `修改配置文件操作ID: ${item.id}`,
|
|
|
+ time: this.formatApiDate(item.createdTime),
|
|
|
+ userId: item.userId,
|
|
|
+ oldFileId: item.oldFileId,
|
|
|
+ newFileId: item.newFileId,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('获取更改列表失败:', error);
|
|
|
+ // Set some default data or show error message
|
|
|
+ this.$message.error('获取更改列表失败,请稍后重试');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.loading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ formatApiDate(dateString) {
|
|
|
+ if (!dateString) return '';
|
|
|
+ const date = new Date(dateString);
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
|
+ },
|
|
|
+ fetchConversations() {
|
|
|
+ this.conversationsLoading = true;
|
|
|
+ axios.get('http://localhost:8080/api/project/qa-records')
|
|
|
+ .then((response) => {
|
|
|
+ if (response.data && response.data.length) {
|
|
|
+ this.conversations = response.data.map((item) => ({
|
|
|
+ id: item.id,
|
|
|
+ type: item.category || '对话',
|
|
|
+ time: this.formatApiDate(item.createdTime),
|
|
|
+ content: `对话ID: ${item.conversationId}`,
|
|
|
+ conversationId: item.conversationId,
|
|
|
+ userId: item.userId,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('获取对话历史失败:', error);
|
|
|
+ this.$message.error('获取对话历史失败,请稍后重试');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.conversationsLoading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ openConfigDialog() {
|
|
|
+ // 重置表单
|
|
|
+ this.configForm = {
|
|
|
+ filePath: '',
|
|
|
+ modifyMethod: '',
|
|
|
+ };
|
|
|
+ // 打开对话框
|
|
|
+ this.dialogVisible = true;
|
|
|
+ },
|
|
|
+ saveConfig() {
|
|
|
+ this.$refs.configFormRef.validate(async (valid) => {
|
|
|
+ if (!valid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.submitting = true;
|
|
|
+ const { filePath, modifyMethod } = this.configForm;
|
|
|
+
|
|
|
+ // 根据选择的修改方式调用不同的接口
|
|
|
+ const url = modifyMethod === 'ast'
|
|
|
+ ? 'http://localhost:8080/config/modify-by-ast'
|
|
|
+ : 'http://localhost:8080/config/modify-by-llm';
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.post(url, filePath, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$message.success(response.data);
|
|
|
+ this.closeConfigDialog();
|
|
|
+ // 修改成功后重新获取更改列表
|
|
|
+ this.fetchChangeList();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('修改配置文件失败:', error);
|
|
|
+ let errorMsg = '修改配置文件失败';
|
|
|
+ if (error.response && error.response.data) {
|
|
|
+ errorMsg = error.response.data;
|
|
|
+ }
|
|
|
+ this.$message.error(errorMsg);
|
|
|
+ } finally {
|
|
|
+ this.submitting = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ loadCommitInfo() {
|
|
|
+ // 这里可以加载提交信息
|
|
|
+ },
|
|
|
+ analyzeProject() {
|
|
|
+ // 重置表单
|
|
|
+ this.analysisForm = {
|
|
|
+ projectPath: '',
|
|
|
+ };
|
|
|
+ this.showAnalysisResult = false;
|
|
|
+ this.formattedAnalysisResult = '';
|
|
|
+ this.analysisDialogVisible = true;
|
|
|
+ },
|
|
|
+ saveAnalysis() {
|
|
|
+ // 创建分析结果记录并保存
|
|
|
+ const analysisContent = this.analysisForm.projectPath
|
|
|
+ ? `项目【${this.analysisForm.projectPath}】分析结果`
|
|
|
+ : '项目分析结果';
|
|
|
+
|
|
|
+ const newAnalysis = {
|
|
|
+ type: '项目分析',
|
|
|
+ time: this.getCurrentTime(),
|
|
|
+ content: analysisContent,
|
|
|
+ // 如果需要,可以保存完整的分析结果
|
|
|
+ fullResult: this.formattedAnalysisResult,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 将分析结果添加到对话历史
|
|
|
+ this.conversations.unshift(newAnalysis);
|
|
|
+ this.$message.success('分析结果已保存');
|
|
|
+ this.closeAnalysisDialog();
|
|
|
+
|
|
|
+ // 重新获取对话历史
|
|
|
+ this.fetchConversations();
|
|
|
+ },
|
|
|
+ askQuestion() {
|
|
|
+ console.log('文件提问');
|
|
|
+ // 重置表单数据
|
|
|
+ this.selectedFile = '';
|
|
|
+ this.questionContent = '';
|
|
|
+ // 文件提问逻辑
|
|
|
+ this.questionDialogVisible = true;
|
|
|
+ },
|
|
|
+ submitQuestion() {
|
|
|
+ if (!this.selectedFile || !this.questionContent) {
|
|
|
+ this.$message.warning('请选择文件并输入问题');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示加载状态
|
|
|
+ this.submitting = true;
|
|
|
+
|
|
|
+ // 准备请求数据
|
|
|
+ const requestData = {
|
|
|
+ filePath: this.selectedFile,
|
|
|
+ question: this.questionContent,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 调用后端API
|
|
|
+ axios.post('http://localhost:8080/api/project/answer-code-question', requestData, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .then((response) => {
|
|
|
+ console.log('提交问题成功', response.data);
|
|
|
+
|
|
|
+ // 创建新的对话记录
|
|
|
+ const newQuestion = {
|
|
|
+ type: '文件提问',
|
|
|
+ time: this.getCurrentTime(),
|
|
|
+ content: `问:${this.questionContent}?答:${response.data.answer || '分析中...'}`,
|
|
|
+ conversationId: response.data.conversation_id,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到对话历史
|
|
|
+ this.conversations.unshift(newQuestion);
|
|
|
+ this.questionDialogVisible = false;
|
|
|
+ this.selectedFile = '';
|
|
|
+ this.questionContent = '';
|
|
|
+
|
|
|
+ // 重新获取对话历史
|
|
|
+ this.fetchConversations();
|
|
|
+
|
|
|
+ this.$message.success('问题提交成功');
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('提交问题失败', error);
|
|
|
+ this.$message.error('问题提交失败,请稍后重试');
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.submitting = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getCurrentTime() {
|
|
|
+ const now = new Date();
|
|
|
+ const year = now.getFullYear();
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(now.getDate()).padStart(2, '0');
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
|
+ },
|
|
|
+ viewChangeDetail(item, index) {
|
|
|
+ // 使用路由导航到详情页面
|
|
|
+ this.$router.push({
|
|
|
+ name: CHANGE_DETAIL,
|
|
|
+ params: { id: item.id || index },
|
|
|
+ query: {
|
|
|
+ description: item.description,
|
|
|
+ time: item.time,
|
|
|
+ userId: item.userId,
|
|
|
+ oldFileId: item.oldFileId,
|
|
|
+ newFileId: item.newFileId,
|
|
|
+ fileChangeId: item.id,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ viewConversationDetail(item, index) {
|
|
|
+ // 实现查看对话详情逻辑
|
|
|
+ console.log('查看对话详情', item, index);
|
|
|
+ this.$router.push({
|
|
|
+ name: CONVERSATION_DETAIL,
|
|
|
+ params: { id: item.id || index },
|
|
|
+ query: {
|
|
|
+ type: item.type,
|
|
|
+ time: item.time,
|
|
|
+ content: item.content,
|
|
|
+ conversationId: item.conversationId,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ closeConfigDialog() {
|
|
|
+ this.dialogVisible = false;
|
|
|
+ if (this.$refs.configFormRef) {
|
|
|
+ this.$refs.configFormRef.resetFields();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ closeAnalysisDialog() {
|
|
|
+ this.analysisDialogVisible = false;
|
|
|
+ this.showAnalysisResult = false;
|
|
|
+ this.analysisLoading = false;
|
|
|
+ this.formattedAnalysisResult = '';
|
|
|
+ },
|
|
|
+ async submitProjectAnalysis() {
|
|
|
+ this.$refs.analysisFormRef.validate(async (valid) => {
|
|
|
+ if (!valid) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.analysisLoading = true;
|
|
|
+ const { projectPath } = this.analysisForm;
|
|
|
+
|
|
|
+ const response = await axios.post('http://localhost:8080/api/project/analyze', projectPath, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.data) {
|
|
|
+ // 使用marked库将Markdown格式转换为HTML
|
|
|
+ this.formattedAnalysisResult = marked.parse(response.data);
|
|
|
+ this.showAnalysisResult = true;
|
|
|
+ } else {
|
|
|
+ this.$message.warning('获取分析结果为空');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('项目分析失败:', error);
|
|
|
+ let errorMessage = error.message || '未知错误';
|
|
|
+ if (error.response && error.response.data) {
|
|
|
+ errorMessage = error.response.data;
|
|
|
+ }
|
|
|
+ this.$message.error(`项目分析失败: ${errorMessage}`);
|
|
|
+ } finally {
|
|
|
+ this.analysisLoading = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.container {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch; /* 改为stretch让子元素撑满高度 */
|
|
|
+ justify-content: center;
|
|
|
+ height: 500px; /* 给容器一个明确的高度 */
|
|
|
+ padding: 20px;
|
|
|
+ border: 1px solid #eee; /* 临时添加,用于调试容器边界 */
|
|
|
+ overflow: hidden; /* 防止容器溢出 */
|
|
|
+}
|
|
|
+
|
|
|
+.card {
|
|
|
+ flex: 1;
|
|
|
+ max-width: 50%;
|
|
|
+ border: 1px solid #ebeef5; /* 确保卡片有可见边框 */
|
|
|
+ overflow: hidden; /* 防止卡片内容溢出 */
|
|
|
+}
|
|
|
+
|
|
|
+.divider {
|
|
|
+ height: 100%;
|
|
|
+ margin: 0 20px;
|
|
|
+ background-color: #dcdfe6; /* 明确设置背景色 */
|
|
|
+}
|
|
|
+
|
|
|
+.left-content, .right-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden; /* 防止内容溢出 */
|
|
|
+}
|
|
|
+
|
|
|
+.change-list-container, .conversation-container {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden; /* 确保容器本身不会溢出 */
|
|
|
+ padding: 0 5px; /* 给滚动条留出空间 */
|
|
|
+}
|
|
|
+
|
|
|
+.change-list, .conversation-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: scroll; /* 强制显示垂直滚动条 */
|
|
|
+ overflow-x: hidden; /* 防止水平滚动 */
|
|
|
+ padding-right: 5px; /* 给滚动条留点额外空间 */
|
|
|
+ margin-bottom: 10px; /* 底部留出一些空间 */
|
|
|
+ max-height: 350px; /* 设置最大高度确保滚动条出现 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义滚动条样式 */
|
|
|
+.change-list::-webkit-scrollbar,
|
|
|
+.conversation-list::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.change-list::-webkit-scrollbar-track,
|
|
|
+.conversation-list::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.change-list::-webkit-scrollbar-thumb,
|
|
|
+.conversation-list::-webkit-scrollbar-thumb {
|
|
|
+ background: #c0c4cc;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.change-list::-webkit-scrollbar-thumb:hover,
|
|
|
+.conversation-list::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.change-item, .conversation-item {
|
|
|
+ padding: 12px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.change-item:hover {
|
|
|
+ background-color: #e6f1fc;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.change-content {
|
|
|
+ color: #606266;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.change-time {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ align-self: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.no-changes, .no-conversations {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧内容样式 */
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-type {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-time {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-content {
|
|
|
+ color: #606266;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话框样式 */
|
|
|
+.dialog-content {
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.question-form {
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.conversation-item:hover {
|
|
|
+ background-color: #e6f1fc;
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.analysis-result {
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.analysis-loading {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content {
|
|
|
+ margin-top: 10px;
|
|
|
+ line-height: 1.6;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ padding: 10px;
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(h1) {
|
|
|
+ font-size: 24px;
|
|
|
+ margin-top: 28px;
|
|
|
+ margin-bottom: 18px;
|
|
|
+ border-bottom: 1px solid #eaecef;
|
|
|
+ padding-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(h2) {
|
|
|
+ font-size: 20px;
|
|
|
+ margin-top: 24px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border-bottom: 1px solid #eaecef;
|
|
|
+ padding-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(h3) {
|
|
|
+ font-size: 18px;
|
|
|
+ margin-top: 20px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(p) {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(ul), .markdown-content :deep(ol) {
|
|
|
+ padding-left: 20px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(li) {
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(code) {
|
|
|
+ background-color: #f6f8fa;
|
|
|
+ padding: 2px 4px;
|
|
|
+ border-radius: 3px;
|
|
|
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(pre) {
|
|
|
+ background-color: #f6f8fa;
|
|
|
+ padding: 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: auto;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(pre code) {
|
|
|
+ background-color: transparent;
|
|
|
+ padding: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(blockquote) {
|
|
|
+ border-left: 4px solid #dfe2e5;
|
|
|
+ padding: 0 16px;
|
|
|
+ color: #6a737d;
|
|
|
+ margin: 0 0 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(table) {
|
|
|
+ border-collapse: collapse;
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(th), .markdown-content :deep(td) {
|
|
|
+ border: 1px solid #dfe2e5;
|
|
|
+ padding: 8px 12px;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(th) {
|
|
|
+ background-color: #f6f8fa;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+</style>
|