| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638 |
- <template>
- <div class="documents-container">
- <div class="header-section">
- <div class="title-info">
- <h2>{{ currentTitle }}</h2>
- <div class="statistics-bar">
- <span class="stat-item">
- <el-icon><Document /></el-icon>
- 全部数据: <span class="stat-value">{{ statistics.allTotal }}</span>
- </span>
- <span class="stat-item">
- <el-icon><CircleCheck /></el-icon>
- 已入库: <span class="stat-value success">{{ statistics.totalEntered }}</span>
- </span>
- <span class="stat-item" v-if="searchQuery.keyword || searchQuery.table_type || searchQuery.whether_to_enter !== null">
- <el-icon><Search /></el-icon>
- 检索结果: <span class="stat-value">{{ total }}</span>
- </span>
- </div>
- </div>
- <div class="action-buttons">
- <el-button type="danger" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
- <el-icon><Delete /></el-icon> 批量删除
- </el-button>
- <el-button type="warning" :disabled="selectedIds.length === 0" @click="handleBatchEnter">
- <el-icon><CircleCheck /></el-icon> 批量入库
- </el-button>
- <el-button type="success" class="upload-btn" @click="handleUpload">
- <el-icon><Upload /></el-icon> 上传文档
- </el-button>
- </div>
- </div>
- <!-- 板块切换 Tabs 移除,改为合并视图 -->
- <el-card class="search-card">
- <div class="search-bar">
- <el-input
- v-model="searchQuery.keyword"
- placeholder="搜索文档名称..."
- class="search-input"
- clearable
- @keyup.enter="handleSearch"
- >
- <template #prefix>
- <el-icon><Search /></el-icon>
- </template>
- </el-input>
- <div class="filter-group">
- <el-select v-model="searchQuery.table_type" placeholder="所属知识库" clearable class="filter-select">
- <el-option label="全部知识库" :value="null" />
- <el-option label="施工标准规范知识库" value="basis" />
- <el-option label="施工方案知识库" value="work" />
- <el-option label="办公制度知识库" value="job" />
- </el-select>
- <el-select v-model="searchQuery.whether_to_enter" placeholder="入库状态" clearable class="filter-select">
- <el-option label="全部状态" :value="null" />
- <el-option label="未入库" :value="0" />
- <el-option label="已入库" :value="1" />
- </el-select>
- <el-button type="primary" @click="handleSearch" class="search-btn">
- <el-icon><Filter /></el-icon> 检索
- </el-button>
- </div>
- </div>
- </el-card>
- <div class="content-section" v-loading="loading">
- <el-empty v-if="documents.length === 0" description="暂无文档数据" />
- <el-table
- v-else
- :data="documents"
- style="width: 100%"
- border
- stripe
- size="small"
- @selection-change="handleSelectionChange"
- :row-class-name="tableRowClassName"
- >
- <el-table-column type="selection" width="40" :selectable="canSelect" />
-
- <el-table-column prop="title" label="文档名称" min-width="280" show-overflow-tooltip>
- <template #default="scope">
- <div class="file-info-cell">
- <div class="file-icon-mini" :class="getFileIconClass(scope.row)">
- <el-icon v-if="getFileIcon(scope.row) === 'pdf'"><Document /></el-icon>
- <el-icon v-else-if="getFileIcon(scope.row) === 'word'"><Document /></el-icon>
- <el-icon v-else-if="getFileIcon(scope.row) === 'excel'"><Grid /></el-icon>
- <el-icon v-else-if="getFileIcon(scope.row) === 'ppt'"><DataAnalysis /></el-icon>
- <el-icon v-else><Document /></el-icon>
- </div>
- <div class="file-info-content">
- <span class="file-name-link" @click="handleView(scope.row)">
- {{ scope.row.title }}{{ getFileExtension(scope.row) }}
- </span>
- <span v-if="scope.row.note" class="file-note">
- {{ scope.row.note }}
- </span>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="转换状态" min-width="180">
- <template #default="scope">
- <div class="conversion-cell">
- <el-tag
- :type="getConversionStatusTag(scope.row)"
- size="small"
- effect="light"
- class="status-tag"
- >
- {{ getConversionStatusText(scope.row) }}
- </el-tag>
-
- <div class="converted-file-links" v-if="scope.row.conversion_status === 2">
- <div class="converted-file-name" v-if="scope.row.md_url">
- <el-link type="primary" :underline="false" @click="handleDownloadConverted(scope.row)">
- <el-icon><Link /></el-icon> {{ scope.row.md_display_name || '下载 Markdown' }}
- </el-link>
- </div>
- <div class="converted-file-name" v-if="scope.row.json_url">
- <el-link type="success" :underline="false" @click="handleDownloadJson(scope.row)">
- <el-icon><Link /></el-icon> {{ scope.row.json_display_name || '下载 JSON' }}
- </el-link>
- </div>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="知识库" min-width="120">
- <template #default="scope">
- <el-tag size="small" effect="plain" :type="getKBTagType(scope.row.source_type)">
- {{ getKnowledgeBaseShortName(scope.row.source_type) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="whether_to_enter" label="入库" width="80" align="center">
- <template #default="scope">
- <el-tooltip :content="isEntered(scope.row.whether_to_enter) ? '已入库' : '未入库'" placement="top">
- <el-icon :class="isEntered(scope.row.whether_to_enter) ? 'status-icon-success' : 'status-icon-info'">
- <CircleCheck v-if="isEntered(scope.row.whether_to_enter)" />
- <Warning v-else />
- </el-icon>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column prop="creator_name" label="上传人" min-width="100" show-overflow-tooltip />
- <el-table-column label="上传时间" min-width="150" prop="created_time">
- <template #default="scope">
- {{ formatDate(scope.row.created_time) }}
- </template>
- </el-table-column>
- <el-table-column prop="updater_name" label="修改人" min-width="100" show-overflow-tooltip />
- <el-table-column label="修改时间" min-width="150" prop="updated_time">
- <template #default="scope">
- {{ formatDate(scope.row.updated_time) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="260" fixed="right" align="center">
- <template #default="scope">
- <div class="action-buttons">
- <el-tooltip content="编辑" placement="top">
- <el-button
- link
- type="primary"
- @click="handleEdit(scope.row)"
- class="action-btn-icon"
- >
- <el-icon><Edit /></el-icon>
- </el-button>
- </el-tooltip>
-
- <el-tooltip content="下载" placement="top" v-if="scope.row.file_url">
- <el-button
- link
- type="success"
- @click="handleDownload(scope.row)"
- class="action-btn-icon"
- >
- <el-icon><Download /></el-icon>
- </el-button>
- </el-tooltip>
-
- <el-tooltip
- :content="scope.row.conversion_status === 1 ? '转换中' : (scope.row.conversion_status === 2 ? '重新转换' : '开始转换')"
- placement="top"
- >
- <el-button
- link
- type="warning"
- @click="handleConvert(scope.row)"
- :disabled="scope.row.conversion_status === 1"
- class="action-btn-icon"
- >
- <el-icon><Switch /></el-icon>
- </el-button>
- </el-tooltip>
- <el-tooltip content="删除" placement="top">
- <el-button
- link
- type="danger"
- @click="handleDelete(scope.row)"
- class="action-btn-icon"
- >
- <el-icon><Delete /></el-icon>
- </el-button>
- </el-tooltip>
- </div>
- </template>
- </el-table-column>
- </el-table>
- <!-- 文档预览对话框 -->
- <el-dialog v-model="previewVisible" :title="previewTitle" width="85%" top="5vh" custom-class="preview-dialog" @closed="previewUrl = ''">
- <template #header>
- <div class="dialog-header-custom">
- <span>{{ previewTitle }}</span>
- <div class="header-actions">
- <el-button type="primary" link @click="openInNewWindow">
- <el-icon><TopRight /></el-icon>在新窗口打开原链接
- </el-button>
- </div>
- </div>
- </template>
- <div v-loading="previewLoading" class="preview-content">
- <!-- Office 文档未转换提示 -->
- <div v-if="isOfficeDoc && previewDocType === 'original' && !previewLoading" class="unsupported-preview">
- <el-result
- icon="warning"
- title="该格式暂不支持直接预览"
- sub-title="Word/Excel/PPT 等 Office 文档需要转换后才能在浏览器中直接预览。"
- >
- <template #extra>
- <div class="unsupported-actions">
- <el-button type="primary" @click="handleConvert(currentDoc!)">
- <el-icon><Switch /></el-icon> 立即转换
- </el-button>
- <el-button type="success" @click="handleDownload(currentDoc!)">
- <el-icon><Download /></el-icon> 下载原文档
- </el-button>
- </div>
- </template>
- </el-result>
- </div>
- <div v-if="isHtmlPage && !previewLoading && !isOfficeDoc" class="preview-tip">
- <el-alert
- title="网页预览提示"
- type="info"
- description="由于外部网站的安全限制,某些网页可能无法在此处正常预览。如果显示异常,请点击右上角按钮在新窗口中查看。"
- show-icon
- :closable="false"
- />
- </div>
- <iframe
- v-if="previewUrl && !(isOfficeDoc && previewDocType === 'original')"
- :src="proxyPreviewUrl"
- width="100%"
- height="100%"
- frameborder="0"
- allow="fullscreen"
- @load="previewLoading = false"
- ></iframe>
- </div>
- </el-dialog>
- <div class="pagination-container" v-if="total > 0">
- <el-pagination
- v-model:current-page="searchQuery.page"
- v-model:page-size="searchQuery.size"
- :page-sizes="[10, 20, 50, 100]"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total"
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- />
- </div>
- </div>
- <!-- 上传文档对话框 -->
- <el-dialog v-model="uploadDialogVisible" title="上传文档" width="500px">
- <el-form :model="uploadForm" label-width="100px">
- <el-form-item label="所属知识库" required>
- <el-select v-model="uploadForm.table_type" placeholder="请选择知识库">
- <el-option label="施工标准规范知识库" value="basis" />
- <el-option label="施工方案知识库" value="work" />
- <el-option label="办公制度知识库" value="job" />
- </el-select>
- </el-form-item>
- <el-form-item label="文档标题" required>
- <el-input v-model="uploadForm.title" placeholder="请输入文档标题" />
- </el-form-item>
- <el-form-item label="文档链接" v-if="false">
- <el-input v-model="uploadForm.file_url" placeholder="请输入文档链接 (URL) 或通过下方上传文件" />
- </el-form-item>
- <el-form-item label="文件上传">
- <el-upload
- ref="uploadRef"
- class="upload-demo"
- action="#"
- :http-request="customUpload"
- :limit="1"
- :on-exceed="handleExceed"
- :before-upload="beforeUpload"
- >
- <el-button type="primary">点击上传</el-button>
- <template #tip>
- <div class="el-upload__tip">
- 支持 PDF, Word, Excel, PPT, TXT 等文件,最大 50MB
- </div>
- </template>
- </el-upload>
- </el-form-item>
- <el-form-item label="文档备注">
- <el-input v-model="uploadForm.note" type="textarea" :rows="4" placeholder="请输入文档备注" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="uploadDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitUpload" :loading="submitting">确定</el-button>
- </template>
- </el-dialog>
- <!-- 编辑文档对话框 -->
- <el-dialog v-model="editDialogVisible" title="编辑文档" width="700px">
- <el-form :model="editForm" label-width="120px">
- <el-divider content-position="left">基础信息</el-divider>
- <el-form-item label="所属知识库" required>
- <el-select v-model="editForm.table_type" disabled placeholder="请选择知识库" style="width: 100%">
- <el-option label="施工标准规范知识库" value="basis" />
- <el-option label="施工方案知识库" value="work" />
- <el-option label="办公制度知识库" value="job" />
- </el-select>
- </el-form-item>
- <el-form-item label="文档标题" required>
- <el-input v-model="editForm.title" placeholder="请输入文档标题" />
- </el-form-item>
- <el-form-item label="文档链接" v-if="false">
- <el-input v-model="editForm.file_url" placeholder="请输入文档链接 (URL)" />
- </el-form-item>
-
- <el-divider content-position="left">专业属性补全</el-divider>
-
- <!-- 施工标准规范特有字段 -->
- <template v-if="editForm.table_type === 'basis'">
- <el-form-item label="标准编号">
- <el-input v-model="editForm.standard_no" placeholder="如:GB/T 50001-2017" />
- </el-form-item>
- <el-form-item label="发布单位">
- <el-input v-model="editForm.issuing_authority" placeholder="请输入发布单位" />
- </el-form-item>
- <el-form-item label="发布日期">
- <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="文件类型">
- <el-input v-model="editForm.document_type" placeholder="如:国家标准" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="时效性">
- <el-select v-model="editForm.validity" placeholder="请选择" style="width: 100%">
- <el-option label="现行" value="现行" />
- <el-option label="已废止" value="已废止" />
- <el-option label="被替代" value="被替代" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="专业领域">
- <el-input v-model="editForm.professional_field" placeholder="如:建筑工程" />
- </el-form-item>
- </template>
- <!-- 施工方案特有字段 -->
- <template v-else-if="editForm.table_type === 'work'">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="工程名称">
- <el-input v-model="editForm.project_name" placeholder="请输入项目名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="工程标段">
- <el-input v-model="editForm.project_section" placeholder="请输入项目标段" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="方案类别">
- <el-select v-model="editForm.plan_category" placeholder="请选择方案类别" style="width: 100%">
- <el-option v-for="item in planCategoryOptions" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="一级分类">
- <el-input v-model="editForm.level_1_classification" disabled />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="二级分类">
- <el-select v-model="editForm.level_2_classification" placeholder="请选择二级分类" style="width: 100%">
- <el-option v-for="item in level2Options" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="三级分类">
- <el-select v-model="editForm.level_3_classification" placeholder="请选择三级分类" style="width: 100%">
- <el-option v-for="item in level3Options" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="四级分类">
- <el-select v-model="editForm.level_4_classification" placeholder="请选择四级分类" style="width: 100%">
- <el-option v-for="item in level4Options" :key="item" :label="item" :value="item" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="编制单位">
- <el-input v-model="editForm.issuing_authority" placeholder="请输入编制单位" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="编制日期">
- <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="方案摘要">
- <el-input v-model="editForm.plan_summary" type="textarea" :rows="3" placeholder="请输入方案摘要" />
- </el-form-item>
- <el-form-item label="施工标准规范">
- <el-input v-model="editForm.compilation_basis" type="textarea" :rows="3" placeholder="请输入施工标准规范,多个依据请用逗号分隔" />
- </el-form-item>
- </template>
- <!-- 办公制度特有字段 -->
- <template v-else-if="editForm.table_type === 'job'">
- <el-form-item label="发布部门">
- <el-input v-model="editForm.issuing_authority" placeholder="请输入发布部门" />
- </el-form-item>
- <el-form-item label="制度类型">
- <el-input v-model="editForm.document_type" placeholder="如:行政管理" />
- </el-form-item>
- <el-form-item label="发布日期">
- <el-date-picker v-model="editForm.release_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- </template>
- <el-divider content-position="left">文档备注</el-divider>
- <el-form-item label="文档备注">
- <el-input v-model="editForm.note" type="textarea" :rows="6" placeholder="请输入文档备注" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="editDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitEdit" :loading="submitting">确定保存</el-button>
- </template>
- </el-dialog>
- <!-- 文档详情对话框 -->
- <el-dialog v-model="detailDialogVisible" title="文档详情" width="600px">
- <el-descriptions :column="1" border>
- <el-descriptions-item label="名称">{{ currentDoc?.title }}</el-descriptions-item>
- <el-descriptions-item label="知识库">{{ getKnowledgeBaseName(currentDoc?.source_type) }}</el-descriptions-item>
- <el-descriptions-item label="上传人">{{ currentDoc?.creator_name || '-' }}</el-descriptions-item>
- <el-descriptions-item label="上传时间">{{ formatDate(currentDoc?.created_time) }}</el-descriptions-item>
- <el-descriptions-item label="最后修改人" v-if="currentDoc?.updater_name">{{ currentDoc?.updater_name }}</el-descriptions-item>
- <el-descriptions-item label="修改时间" v-if="currentDoc?.updated_time">{{ formatDate(currentDoc?.updated_time) }}</el-descriptions-item>
- <el-descriptions-item label="入库状态">
- <el-tag :type="isEntered(currentDoc?.whether_to_enter) ? 'success' : 'info'">
- {{ isEntered(currentDoc?.whether_to_enter) ? '已入库' : '未入库' }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="文档备注">
- <div class="content-preview">{{ currentDoc?.note || '暂无备注' }}</div>
- </el-descriptions-item>
-
- <!-- 施工方案特有详情 -->
- <template v-if="currentDoc?.source_type === 'work'">
- <el-descriptions-item label="工程名称">{{ currentDoc?.project_name || '-' }}</el-descriptions-item>
- <el-descriptions-item label="工程标段">{{ currentDoc?.project_section || '-' }}</el-descriptions-item>
- <el-descriptions-item label="方案类别">{{ currentDoc?.plan_category || '-' }}</el-descriptions-item>
- <el-descriptions-item label="一级分类">{{ currentDoc?.level_1_classification || '-' }}</el-descriptions-item>
- <el-descriptions-item label="二级分类">{{ currentDoc?.level_2_classification || '-' }}</el-descriptions-item>
- <el-descriptions-item label="三级分类">{{ currentDoc?.level_3_classification || '-' }}</el-descriptions-item>
- <el-descriptions-item label="四级分类">{{ currentDoc?.level_4_classification || '-' }}</el-descriptions-item>
- <el-descriptions-item label="编制单位">{{ currentDoc?.issuing_authority || '-' }}</el-descriptions-item>
- <el-descriptions-item label="编制日期">{{ formatDate(currentDoc?.release_date) }}</el-descriptions-item>
- <el-descriptions-item label="方案摘要">
- <div class="content-preview">{{ currentDoc?.plan_summary || '-' }}</div>
- </el-descriptions-item>
- <el-descriptions-item label="施工标准规范">
- <div class="content-preview">{{ currentDoc?.compilation_basis || '-' }}</div>
- </el-descriptions-item>
- </template>
- </el-descriptions>
- <template #footer>
- <div class="detail-footer">
- <div class="download-group" v-if="currentDoc">
- <el-button type="success" plain size="small" @click="handleDownload(currentDoc)" v-if="currentDoc.file_url">
- <el-icon><Download /></el-icon> 下载原文件
- </el-button>
- <el-button type="primary" plain size="small" @click="handleDownloadConverted(currentDoc)" v-if="currentDoc.md_url">
- <el-icon><Download /></el-icon> 下载 MD
- </el-button>
- <el-button type="warning" plain size="small" @click="handleDownloadJson(currentDoc)" v-if="currentDoc.json_url">
- <el-icon><Download /></el-icon> 下载 JSON
- </el-button>
- </div>
- <div class="action-group">
- <el-button @click="detailDialogVisible = false">关闭</el-button>
- <el-button type="primary" @click="handleEditFromDetail" v-if="currentDoc">
- <el-icon><Edit /></el-icon> 编辑文档
- </el-button>
- <el-button type="success" @click="handleSingleEnter(currentDoc)" v-if="currentDoc && !isEntered(currentDoc.whether_to_enter)">加入知识库</el-button>
- <el-button type="primary" @click="handlePreview(currentDoc)" v-if="currentDoc?.file_url">预览原文档</el-button>
- </div>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { Search, Filter, Upload, CircleCheck, Delete, Document, Warning, TopRight, Grid, DataAnalysis, Link, View, Switch, Edit, User, Download } from '@element-plus/icons-vue'
- import request from '@/api/request'
- import axios from 'axios'
- import { downloadFile } from '@/utils/download'
- import { getFileExtension } from '@/utils/file'
- import { useAuthStore } from '@/stores/auth'
- import dayjs from 'dayjs'
- import { documentApi, type DocumentItem } from '@/api/document'
- // 接口定义已移至 @/api/document
- // 状态变量
- const loading = ref(false)
- const submitting = ref(false)
- const uploadRef = ref<any>(null)
- const uploadDialogVisible = ref(false)
- const editDialogVisible = ref(false)
- const detailDialogVisible = ref(false)
- const previewVisible = ref(false)
- const previewLoading = ref(false)
- const previewTitle = ref('')
- const previewUrl = ref('')
- const previewDocType = ref('') // 'original' or 'md'
- const isOfficeDoc = ref(false)
- const total = ref(0)
- const statistics = ref({
- allTotal: 0,
- totalEntered: 0
- })
- const authStore = useAuthStore()
- const documents = ref<DocumentItem[]>([])
- const currentDoc = ref<DocumentItem | null>(null)
- const editForm = reactive({
- id: '',
- title: '',
- note: '',
- table_type: 'basis' as 'basis' | 'work' | 'job',
- year: new Date().getFullYear(),
- // 扩展字段 (子表特有属性)
- standard_no: '',
- issuing_authority: '',
- release_date: '',
- document_type: '',
- professional_field: '',
- validity: '',
- project_name: '',
- project_section: '',
- plan_category: '',
- level_1_classification: '施工方案',
- level_2_classification: '',
- level_3_classification: '',
- level_4_classification: '',
- plan_summary: '',
- compilation_basis: '',
- file_url: ''
- })
- const planCategoryOptions = ['超危大方案', '超危大方案较大II级', '超危大方案特大IV级', '超危大方案一般I级', '超危大方案重大III级', '危大方案', '一般方案']
- const level2Options = ['临建工程', '路基工程', '其他', '桥梁工程', '隧道工程']
- const level3Options = ['/', 'TBM施工', '拌和站安、拆施工', '不良地质隧道施工', '常规桥梁', '挡土墙工程类', '辅助坑道施工', '复杂洞口工程施工', '钢筋加工场安、拆', '钢栈桥施工', '拱桥', '涵洞工程类', '滑坡体处理类', '路堤', '路堑', '其他', '深基坑', '隧道总体施工', '特殊结构隧道', '斜拉桥', '悬索桥']
- const level4Options = ['/', '挡土墙', '顶管', '断层破碎带及软弱围岩', '钢筋砼箱涵', '高填路堤', '抗滑桩', '其他', '软岩大变形隧道', '上部结构', '深基坑开挖与支护', '深挖路堑', '隧道TBM', '隧道进洞', '隧道竖井', '隧道斜井', '特种设备', '瓦斯隧道', '下部结构', '小净距隧道', '岩爆隧道', '岩溶隧道', '涌水突泥隧道', '桩基础']
- const currentTitle = computed(() => {
- return '文档管理中心'
- })
- const selectedIds = ref<string[]>([])
- const searchQuery = reactive({
- keyword: '',
- table_type: null as string | null,
- whether_to_enter: null as number | null,
- page: 1,
- size: 10
- })
- const uploadForm = reactive({
- title: '',
- note: '',
- file_url: '',
- table_type: 'basis' as 'basis' | 'work' | 'job',
- year: new Date().getFullYear()
- })
- // 计算属性
- const proxyPreviewUrl = computed(() => {
- if (!previewUrl.value) return ''
- // 使用后端代理接口查看外部网页,附加 token 以通过认证
- let baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
- if (baseUrl.includes('localhost') && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
- baseUrl = baseUrl.replace('localhost', window.location.hostname)
- }
- return `${baseUrl}/api/v1/sample/documents/proxy-view?url=${encodeURIComponent(previewUrl.value)}&token=${authStore.token}`
- })
- const isHtmlPage = computed(() => {
- if (!previewUrl.value) return false
- const url = previewUrl.value.toLowerCase()
- return url.includes('html') || url.includes('newgbinfo') || !url.split(/[?#]/)[0].includes('.')
- })
- // 方法
- const formatSimpleDate = (date: string | null) => {
- if (!date) return '-'
- return dayjs(date).format('YYYY-MM-DD')
- }
- const formatDate = (date: string | undefined | null, formatStr: string = 'YYYY-MM-DD HH:mm:ss') => {
- return date ? dayjs(date).format(formatStr) : '-'
- }
- const getFileIcon = (row: DocumentItem) => {
- const ext = getFileExtension(row).replace('.', '').toLowerCase()
- if (['pdf'].includes(ext)) return 'pdf'
- if (['doc', 'docx'].includes(ext)) return 'word'
- if (['xls', 'xlsx'].includes(ext)) return 'excel'
- if (['ppt', 'pptx'].includes(ext)) return 'ppt'
- if (['html', 'htm'].includes(ext)) return 'html'
- return 'file'
- }
- const getFileIconClass = (row: DocumentItem) => {
- return `icon-${getFileIcon(row)}`
- }
- const getKnowledgeBaseName = (sourceType: string | null | undefined) => {
- const names: Record<string, string> = {
- basis: '施工标准规范知识库',
- work: '施工方案知识库',
- job: '办公制度知识库'
- }
- return names[sourceType || ''] || '未知知识库'
- }
- const getKnowledgeBaseShortName = (sourceType: string | null | undefined) => {
- const names: Record<string, string> = {
- basis: '施工标准规范',
- work: '施工方案',
- job: '办公制度'
- }
- return names[sourceType || ''] || '未知'
- }
- const getKBTagType = (sourceType: string | null | undefined) => {
- const types: Record<string, string> = {
- basis: 'primary',
- work: 'success',
- job: 'warning'
- }
- return types[sourceType || ''] || 'info'
- }
- // 为已入库或未转化的行添加特定类名
- const tableRowClassName = ({ row }: { row: DocumentItem }) => {
- return ''
- }
- // 判断是否可以勾选(所有文档均可勾选,用于批量删除等操作)
- const canSelect = (row: DocumentItem) => {
- return true
- }
- const handleSelectionChange = (selection: DocumentItem[]) => {
- selectedIds.value = selection.map(item => item.id)
- }
- const handleBatchEnter = async () => {
- if (selectedIds.value.length === 0) return
-
- const ids = [...selectedIds.value]
-
- try {
- const res = await documentApi.batchEnter(ids)
- if (res.code === 0) {
- // 如果有详细详情(换行符标识),使用 MessageBox 显示
- if (res.message && res.message.includes('\n')) {
- ElMessageBox.alert(res.message, '入库结果', {
- confirmButtonText: '确定',
- customStyle: { 'white-space': 'pre-wrap' },
- type: res.message.includes('失败') || res.message.includes('跳过') ? 'warning' : 'success'
- })
- } else {
- ElMessage.success(res.message || '入库成功')
- }
- fetchDocuments()
- } else {
- ElMessageBox.alert(res.message || '入库失败', '操作失败', {
- confirmButtonText: '确定',
- type: 'error'
- })
- }
- } catch (error) {
- console.error('批量入库失败:', error)
- ElMessage.error('网络连接异常,请稍后重试')
- }
- }
- const handleSingleEnter = async (doc: DocumentItem | null) => {
- if (!doc) return
-
- try {
- const res = await documentApi.batchEnter([doc.id])
- if (res.code === 0) {
- // 无论成功失败,只要有详细消息就弹出提示框
- if (res.message && (res.message.includes('\n') || res.message.includes('失败') || res.message.includes('跳过'))) {
- ElMessageBox.alert(res.message, '入库结果', {
- confirmButtonText: '确定',
- customStyle: { 'white-space': 'pre-wrap' },
- type: res.message.includes('失败') || res.message.includes('跳过') ? 'warning' : 'success'
- })
- } else {
- ElMessage.success(res.message || '入库成功')
- }
- detailDialogVisible.value = false
- fetchDocuments()
- } else {
- ElMessageBox.alert(res.message || '入库失败', '操作失败', {
- confirmButtonText: '确定',
- type: 'error'
- })
- }
- } catch (error) {
- console.error('入库失败:', error)
- ElMessage.error('入库异常,请检查网络连接')
- }
- }
- const handleDelete = async (row: DocumentItem) => {
- try {
- await ElMessageBox.confirm(
- `确定要删除文档 "${row.title}" 吗?此操作不可恢复。`,
- '确认删除',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
-
- const res = await documentApi.batchDelete([row.id])
-
- if (res.code === 0) {
- ElMessage.success('删除成功')
- fetchDocuments()
- } else {
- ElMessage.error(res.message || '删除失败')
- }
- } catch (error: any) {
- if (error !== 'cancel') {
- console.error('删除文档失败:', error)
- ElMessage.error('删除失败')
- }
- }
- }
- const handleBatchDelete = async () => {
- if (selectedIds.value.length === 0) return
-
- try {
- await ElMessageBox.confirm(
- `确定要批量删除选中的 ${selectedIds.value.length} 条文档吗?此操作不可恢复。`,
- '确认批量删除',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
-
- const res = await documentApi.batchDelete(selectedIds.value)
-
- if (res.code === 0) {
- ElMessage.success(res.message || '批量删除成功')
- selectedIds.value = []
- fetchDocuments()
- } else {
- ElMessage.error(res.message || '批量删除失败')
- }
- } catch (error: any) {
- if (error !== 'cancel') {
- console.error('批量删除失败:', error)
- ElMessage.error('操作失败')
- }
- }
- }
- const isEntered = (val: any) => {
- return val === 1 || val === true
- }
- const getConversionStatusTag = (row: DocumentItem) => {
- switch (row.conversion_status) {
- case 1: return 'warning' // 转换中
- case 2: return 'success' // 成功
- case 3: return 'danger' // 失败
- default: return 'info' // 未转换 (0)
- }
- }
- const getConversionStatusText = (row: DocumentItem) => {
- switch (row.conversion_status) {
- case 1: return '转换中'
- case 2: return '转换成功'
- case 3: return '转换失败'
- default: return '未转换'
- }
- }
- const fetchDocuments = async () => {
- loading.value = true
- try {
- const res = await documentApi.getList(searchQuery)
- if (res.code === 0) {
- documents.value = res.data.items
- total.value = res.data.total
- statistics.value.allTotal = res.data.all_total || 0
- statistics.value.totalEntered = res.data.total_entered || 0
-
- // 自动检查是否需要开启轮询
- const hasConverting = documents.value.some(doc => doc.conversion_status === 1)
- if (hasConverting) {
- startPolling()
- } else {
- stopPolling()
- }
- }
- } catch (error) {
- console.error('获取文档列表失败:', error)
- } finally {
- loading.value = false
- }
- }
- const isRefreshing = ref(false)
- const refreshTimer = ref<any>(null)
- const startPolling = () => {
- if (refreshTimer.value === null) {
- refreshTimer.value = window.setTimeout(refreshDocumentsSilently, 5000)
- }
- }
- const stopPolling = () => {
- if (refreshTimer.value) {
- window.clearTimeout(refreshTimer.value)
- refreshTimer.value = null
- }
- }
- const refreshDocumentsSilently = async () => {
- if (isRefreshing.value) return
- isRefreshing.value = true
- try {
- const res = await documentApi.getList(searchQuery, true)
- if (res.code === 0) {
- documents.value = res.data.items
- total.value = res.data.total
- statistics.value.allTotal = res.data.all_total || 0
- statistics.value.totalEntered = res.data.total_entered || 0
- }
- } catch (error) {
- console.error('静默刷新失败:', error)
- } finally {
- isRefreshing.value = false
- // 如果没有手动停止,且组件未卸载,则安排下一次刷新
- if (refreshTimer.value !== null) {
- refreshTimer.value = window.setTimeout(refreshDocumentsSilently, 5000)
- }
- }
- }
- const handleSearch = () => {
- searchQuery.page = 1
- fetchDocuments()
- }
- const handleSizeChange = (val: number) => {
- searchQuery.size = val
- fetchDocuments()
- }
- const handleCurrentChange = (val: number) => {
- searchQuery.page = val
- fetchDocuments()
- }
- const beforeUpload = (file: File) => {
- const isLt50M = file.size / 1024 / 1024 < 50
- if (!isLt50M) {
- ElMessage.error('上传文件大小不能超过 50MB!')
- return false
- }
- return true
- }
- const handleExceed = () => {
- ElMessage.warning('只能上传一个文件,请先移除已上传的文件')
- }
- const customUpload = async (options: any) => {
- const { file, onSuccess, onError } = options
- try {
- // 1. 获取预签名 URL
- const res = await documentApi.getUploadUrl(file.name, file.type || 'application/octet-stream')
- if (res.code !== 0) {
- throw new Error(res.message || '获取上传链接失败')
- }
- const { upload_url, file_url } = res.data
- // 2. 直接上传到 MinIO (PUT 请求)
- await axios.put(upload_url, file, {
- headers: {
- 'Content-Type': file.type || 'application/octet-stream'
- }
- })
- // 3. 上传成功,更新表单
- uploadForm.file_url = file_url
- if (!uploadForm.title) {
- // 如果标题为空,自动填充文件名(去掉后缀)
- uploadForm.title = file.name.replace(/\.[^/.]+$/, "")
- }
-
- ElMessage.success('文件上传成功')
- onSuccess(res.data)
- } catch (error: any) {
- console.error('文件上传失败:', error)
- ElMessage.error(error.message || '文件上传失败')
- onError(error)
- }
- }
- const handleUpload = () => {
- uploadForm.title = ''
- uploadForm.note = ''
- uploadForm.file_url = ''
- if (uploadRef.value) {
- uploadRef.value.clearFiles()
- }
- uploadDialogVisible.value = true
- }
- const submitUpload = async () => {
- if (!uploadForm.title) {
- return ElMessage.warning('请输入文档标题')
- }
- submitting.value = true
- try {
- const res = await documentApi.add(uploadForm)
- if (res.code === 0) {
- ElMessage.success('上传成功')
- uploadDialogVisible.value = false
- fetchDocuments()
- }
- } catch (error) {
- console.error('上传失败:', error)
- } finally {
- submitting.value = false
- }
- }
- const handleEdit = async (row: DocumentItem) => {
- try {
- const res = await documentApi.getDetail(row.id)
- if (res.code === 0 && res.data) {
- const data = res.data
- editForm.id = data.id
- editForm.title = data.title
- editForm.note = data.note || ''
- editForm.table_type = data.source_type
-
- // 填充扩展字段
- editForm.standard_no = data.standard_no || ''
- editForm.issuing_authority = data.issuing_authority || ''
- editForm.release_date = data.release_date || ''
- editForm.document_type = data.document_type || ''
- editForm.professional_field = data.professional_field || ''
- editForm.validity = data.validity || ''
- editForm.project_name = data.project_name || ''
- editForm.project_section = data.project_section || ''
- editForm.plan_category = data.plan_category || ''
- editForm.level_1_classification = data.level_1_classification || '施工方案'
- editForm.level_2_classification = data.level_2_classification || ''
- editForm.level_3_classification = data.level_3_classification || ''
- editForm.level_4_classification = data.level_4_classification || ''
- editForm.plan_summary = data.plan_summary || ''
- editForm.compilation_basis = data.compilation_basis || ''
- editForm.file_url = data.file_url || ''
-
- editDialogVisible.value = true
- } else {
- ElMessage.error(res.message || '获取文档详情失败')
- }
- } catch (error) {
- console.error('获取文档详情失败:', error)
- ElMessage.error('获取文档详情失败')
- }
- }
- const submitEdit = async () => {
- if (!editForm.title) {
- return ElMessage.warning('请输入文档标题')
- }
- submitting.value = true
- try {
- const res = await documentApi.edit(editForm)
- if (res.code === 0) {
- ElMessage.success('更新成功')
- editDialogVisible.value = false
- fetchDocuments()
- }
- } catch (error) {
- console.error('编辑失败:', error)
- } finally {
- submitting.value = false
- }
- }
- const handleView = (row: DocumentItem) => {
- if (row.file_url) {
- handlePreview(row)
- } else {
- currentDoc.value = row
- detailDialogVisible.value = true
- }
- }
- const handleEditFromDetail = () => {
- if (currentDoc.value) {
- const doc = currentDoc.value
- detailDialogVisible.value = false
- handleEdit(doc)
- }
- }
- const handlePreview = (row: DocumentItem | null) => {
- if (!row || !row.file_url) return
- currentDoc.value = row
- previewTitle.value = row.title
-
- const ext = getFileExtension(row).toLowerCase()
- const officeExtensions = ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
- isOfficeDoc.value = officeExtensions.includes(ext)
-
- // 如果是 Office 文档且有转换后的 MD,优先预览 MD
- if (isOfficeDoc.value && row.md_url) {
- previewUrl.value = row.md_url
- previewDocType.value = 'md'
- previewTitle.value = `${row.title} (转换预览)`
- } else {
- previewUrl.value = row.file_url
- previewDocType.value = 'original'
- }
-
- previewVisible.value = true
- // 如果是无法直接预览的 Office 文档,不需要等待 iframe 加载
- if (isOfficeDoc.value && previewDocType.value === 'original') {
- previewLoading.value = false
- } else {
- previewLoading.value = true
- }
- }
- const handleDownload = (row: DocumentItem) => {
- if (row.file_url) {
- const ext = getFileExtension(row)
- const filename = row.title.endsWith(ext) ? row.title : `${row.title}${ext}`
- downloadFile(row.file_url, filename)
- } else {
- ElMessage.warning('该文档暂无下载链接')
- }
- }
- const handleDownloadConverted = (row: DocumentItem) => {
- if (row.md_url) {
- const filename = row.md_display_name || `${row.title}.md`
- downloadFile(row.md_url, filename)
- } else {
- ElMessage.warning('该文档暂无转换后的文件')
- }
- }
- const handleDownloadJson = (row: DocumentItem) => {
- if (row.json_url) {
- const filename = row.json_display_name || `${row.title}.json`
- downloadFile(row.json_url, filename)
- } else {
- ElMessage.warning('该文档暂无转换后的 JSON 文件')
- }
- }
- const handleConvert = async (row: DocumentItem) => {
- // 如果已经转换成功(status 为 2),弹出确认框
- if (row.conversion_status === 2) {
- try {
- await ElMessageBox.confirm(
- '该文档已经转换完成,再次转换将覆盖原转换后端文件,你继续吗?',
- '再次转换确认',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- )
- } catch (error) {
- // 用户取消
- return
- }
- }
- try {
- // 乐观更新:设置状态为转换中
- row.conversion_status = 1
- row.conversion_error = undefined
-
- // 启动转换任务(静默刷新,不触发全局 loading)
- const res = await documentApi.convert(row.id)
- if (res.code === 0) {
- ElMessage.success(res.message || '转换任务已启动')
- // 触发一次静默刷新以更新状态
- refreshDocumentsSilently()
- } else {
- // 失败了恢复状态并刷新
- refreshDocumentsSilently()
- }
- } catch (error) {
- console.error('启动转换失败:', error)
- // 失败了恢复状态并刷新
- refreshDocumentsSilently()
- }
- }
- const openInNewWindow = () => {
- if (previewUrl.value) {
- // 优先尝试在新窗口打开代理后的链接,这有助于控制 Content-Disposition
- window.open(proxyPreviewUrl.value, '_blank')
- }
- }
- onMounted(() => {
- fetchDocuments()
- // 5秒后开始第一次静默刷新,之后递归调用
- refreshTimer.value = window.setTimeout(refreshDocumentsSilently, 5000)
- })
- onUnmounted(() => {
- if (refreshTimer.value) {
- window.clearTimeout(refreshTimer.value)
- }
- stopPolling()
- })
- </script>
- <style scoped>
- .documents-container {
- padding: 20px;
- }
- .header-section {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- }
- .title-info h2 {
- margin: 0;
- font-size: 24px;
- color: #303133;
- }
- .statistics-bar {
- display: flex;
- gap: 20px;
- margin-top: 8px;
- }
- .conversion-progress-wrapper {
- width: 100%;
- padding: 0 5px;
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .error-msg-text {
- font-size: 12px;
- color: #f56c6c;
- display: flex;
- align-items: center;
- gap: 4px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-top: 2px;
- }
- .stat-item {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 14px;
- color: #606266;
- }
- .stat-item .el-icon {
- font-size: 16px;
- color: #909399;
- }
- .stat-value {
- font-weight: bold;
- color: #303133;
- }
- .stat-value.success {
- color: #67c23a;
- }
- .clickable-filename {
- color: #409eff;
- cursor: pointer;
- font-weight: 500;
- transition: color 0.2s;
- }
- .clickable-filename:hover {
- color: #66b1ff;
- text-decoration: underline;
- }
- .action-btn {
- padding: 4px 8px;
- height: auto;
- font-size: 13px;
- display: flex;
- align-items: center;
- gap: 4px;
- }
- .action-btn-icon {
- padding: 4px;
- height: 28px;
- width: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all 0.2s;
- }
- .action-btn-icon:hover {
- background-color: #f5f7fa;
- }
- .action-btn-icon .el-icon {
- font-size: 16px;
- }
- .action-btn .el-icon {
- font-size: 14px;
- }
- .file-info-cell {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .file-icon-mini {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 4px;
- font-size: 14px;
- }
- .file-icon-mini.pdf { background-color: #fef0f0; color: #f56c6c; }
- .file-icon-mini.word { background-color: #ecf5ff; color: #409eff; }
- .file-icon-mini.excel { background-color: #f0f9eb; color: #67c23a; }
- .file-icon-mini.ppt { background-color: #fff7e6; color: #e6a23c; }
- .file-info-content {
- display: flex;
- flex-direction: column;
- gap: 4px;
- overflow: hidden;
- }
- .file-note {
- font-size: 12px;
- color: #909399;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 1.2;
- }
- .file-name-link {
- color: #409eff;
- cursor: pointer;
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .file-name-link:hover {
- text-decoration: underline;
- }
- .compact-info {
- display: flex;
- flex-direction: column;
- gap: 2px;
- line-height: 1.2;
- }
- .info-row {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 12px;
- color: #606266;
- }
- .info-row.secondary {
- color: #909399;
- font-size: 11px;
- }
- .date-cell {
- display: flex;
- flex-direction: column;
- line-height: 1.2;
- }
- .time-mini {
- font-size: 11px;
- color: #909399;
- }
- .conversion-cell {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 4px;
- padding: 2px 0;
- }
- .status-tag {
- font-weight: 500;
- margin-bottom: 2px;
- }
- .converted-file-links {
- display: flex;
- flex-direction: column;
- gap: 0px;
- width: 100%;
- }
- .converted-file-name {
- font-size: 12px;
- line-height: 1.2;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .converted-file-name :deep(.el-link) {
- font-size: 12px;
- justify-content: flex-start;
- }
- .converted-file-name :deep(.el-link .el-icon) {
- margin-right: 4px;
- }
- .file-name-mini {
- font-size: 11px;
- color: #909399;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .status-icon-success { color: #67c23a; font-size: 18px; }
- .status-icon-info { color: #909399; font-size: 18px; }
- .upload-btn {
- background-color: #67c23a;
- border-color: #67c23a;
- }
- .search-card {
- margin-bottom: 20px;
- background-color: #f8f9fa;
- }
- .search-bar {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .search-input :deep(.el-input__wrapper) {
- padding: 8px 12px;
- font-size: 16px;
- }
- .filter-group {
- display: flex;
- gap: 12px;
- align-items: center;
- flex-wrap: wrap;
- }
- .filter-select {
- width: 200px;
- }
- .filter-select-year {
- width: 140px;
- }
- .search-btn {
- padding: 0 30px;
- height: 40px;
- font-size: 15px;
- font-weight: bold;
- margin-left: auto;
- }
- .content-section {
- background: #fff;
- border-radius: 4px;
- min-height: 400px;
- }
- .pagination-container {
- margin-top: 20px;
- display: flex;
- justify-content: flex-end;
- }
- /* 文件信息单元格布局 */
- .file-info-cell {
- display: flex;
- align-items: center;
- padding: 8px 0;
- }
- .file-icon-wrapper {
- position: relative;
- width: 40px;
- height: 48px;
- margin-right: 16px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- flex-shrink: 0;
- transition: transform 0.2s;
- }
- .file-icon-wrapper:hover {
- transform: scale(1.05);
- }
- .file-icon-wrapper .el-icon {
- font-size: 24px;
- margin-bottom: 2px;
- color: #fff;
- }
- .file-type-label {
- font-size: 10px;
- font-weight: bold;
- color: #fff;
- text-transform: uppercase;
- }
- /* 不同文件类型的背景色 */
- .icon-pdf { background-color: #ff4d4f; }
- .icon-word { background-color: #1890ff; }
- .icon-excel { background-color: #52c41a; }
- .icon-ppt { background-color: #fa8c16; }
- .icon-html { background-color: #13c2c2; }
- .icon-file { background-color: #8c8c8c; }
- .file-text-content {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .file-name-title {
- font-size: 15px;
- font-weight: 500;
- color: #303133;
- margin-bottom: 4px;
- cursor: pointer;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- transition: color 0.2s;
- }
- .file-name-title:hover {
- color: #409eff;
- }
- .file-description-subtitle {
- font-size: 12px;
- color: #909399;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- /* 操作按钮样式 */
- .action-buttons {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 4px;
- }
- .dialog-header-custom {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-right: 30px;
- }
- .header-actions {
- display: flex;
- gap: 10px;
- }
- .preview-tip {
- margin-bottom: 15px;
- }
- .preview-content {
- height: 75vh;
- display: flex;
- flex-direction: column;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- background-color: #f5f7fa;
- position: relative;
- }
- .unsupported-preview {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #fff;
- }
- .unsupported-actions {
- display: flex;
- gap: 12px;
- justify-content: center;
- }
- .preview-content iframe {
- flex: 1;
- display: block;
- }
- :deep(.preview-dialog) {
- .el-dialog__body {
- padding: 10px 20px 20px;
- }
- }
- </style>
|