OCRDialog.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. <template>
  2. <el-dialog :title="title" v-model="dialogFormVisible" @closed="handleClosed">
  3. <el-form :model="form" ref="formRef">
  4. <el-form-item label="任务名称" prop="name" :label-width="formLabelWidth" required>
  5. <el-input v-model="form.name" autocomplete="off"></el-input>
  6. </el-form-item>
  7. <el-form-item label="任务状态" :label-width="formLabelWidth">
  8. <el-select v-model="form.status" placeholder="请选择任务状态">
  9. <el-option label="排队" value="queue"></el-option>
  10. <el-option label="立即执行" value="immediately"></el-option>
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="文件" :label-width="formLabelWidth">
  14. <el-upload ref="upload" :action="'/api/file/upload/pdf/' + form.job_id" :mutiple="false" :limit="100"
  15. :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" v-model:file-list="fileList"
  16. :on-exceed="handleExceed" :auto-upload="false">
  17. <el-button @click.stop="knowledgeBase.visible = true" size="small" type="primary">知识库导入</el-button>
  18. <el-button slot="trigger" size="small" type="primary">上传文件</el-button>
  19. <div slot="tip" class="el-upload__tip">只能上传pdf文件,且不超过500kb</div>
  20. </el-upload>
  21. </el-form-item>
  22. </el-form>
  23. <div slot="footer">
  24. <el-button type="primary" @click="handleConfirm">确 定</el-button>
  25. <el-button @click="dialogFormVisible = false">取 消</el-button>
  26. </div>
  27. <div class="knowledge-base" v-show="knowledgeBase.visible">
  28. <div class="topbar"><span @click="knowledgeBase.visible = false" class="close-knowledge-base"><el-icon>
  29. <Close />
  30. </el-icon></span>
  31. </div>
  32. <div class="knowledge-base-content">
  33. <el-scrollbar class="knowledge-base-list">
  34. <div class="knowledge-base-item">
  35. <span class="name">知识库名称</span>
  36. <span class="number">文件数量</span>
  37. </div>
  38. <div :class="knowledgeBase.activeId === item.id ? 'knowledge-base-item active' : 'knowledge-base-item'"
  39. v-for="item in knowledgeBase.data" :key="item.id" @click="handleKnowledgeBaseClick(toRaw(item))">
  40. <span class="name">
  41. <span class="icon">
  42. <span class="document-icon"></span>
  43. </span>
  44. <span class="text">
  45. <div class="name-text">{{ item.name }}</div>
  46. <div class="description-text">{{ item.description }}</div>
  47. </span>
  48. </span>
  49. <span class="number">{{ item.file_count }}</span>
  50. </div>
  51. </el-scrollbar>
  52. <div class="knowledge-base-detail">
  53. <div class="management-content">
  54. <div class="management-content-top">
  55. <span class="search">
  56. <el-input v-model="knowledgeBase.querySearch" size="large" placeholder="搜索"
  57. @keydown.enter="debounceGetKBfileList" :prefix-icon="Search" />
  58. </span>
  59. <span class="add-file" @click="handleSelectedImport()">
  60. <span class="text">批量导入</span>
  61. </span>
  62. </div>
  63. <el-scrollbar class="management-content-middle">
  64. <el-table :data="knowledgeBase.filesList" ref="KBTableRef">
  65. <el-table-column type="selection" width="30" />
  66. <el-table-column label="#" prop="index" width="50" />
  67. <el-table-column prop="file_name" min-width="150" label="标题">
  68. <template #default="{ row }">
  69. <div class="list-name">
  70. <span class="icon-area">
  71. <i :class="`${row.file_type}-icon`"></i>
  72. </span>
  73. <span class="text-area">
  74. {{ row.file_name }}
  75. </span>
  76. </div>
  77. </template>
  78. </el-table-column>
  79. <el-table-column prop="knowledge_type" label="知识类型" />
  80. <el-table-column prop="version" label="版本" />
  81. <el-table-column prop="author" label="作者(主编)" />
  82. <el-table-column prop="year" label="年份" />
  83. <el-table-column prop="creator" label="上传人" />
  84. <el-table-column prop="created_at" label="上传时间" width="150" />
  85. <el-table-column prop="" label="状态" width="80">
  86. <template #default="{ row }">
  87. <span v-if="row.isValid">
  88. <i class="circle" type="success"></i>
  89. <el-text type="success">可用</el-text>
  90. </span>
  91. <span v-else>
  92. <i class="circle" type="warning"></i>
  93. <el-text type="warning">异常</el-text>
  94. </span>
  95. </template>
  96. </el-table-column>
  97. <el-table-column fixed="right" label="操作" min-width="55">
  98. <template #default="{ row }">
  99. <div class="operation">
  100. <el-button link type="primary" @click="handleImportFiles([toRaw(row)])">导入</el-button>
  101. </div>
  102. </template>
  103. </el-table-column>
  104. </el-table>
  105. <div class="pagination"
  106. v-if="totalPage > 1 || paginationData.defaultPageSize !== paginationData.currentPageSize">
  107. <el-pagination :current-page="paginationData.currentPage" :page-sizes="paginationData.pageSizes"
  108. :disabled="paginationData.disabled" :default-page-size="paginationData.defaultPageSize"
  109. layout="total, sizes, prev, pager, next, jumper" :total="paginationData.total"
  110. @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  111. </div>
  112. </el-scrollbar>
  113. </div>
  114. </div>
  115. </div>
  116. </div>
  117. </el-dialog>
  118. </template>
  119. <script setup lang="ts">
  120. import { ref, watch, toRaw, onMounted, computed, getCurrentInstance } from 'vue'
  121. import { Search } from '@element-plus/icons-vue'
  122. import CryptoJS from 'crypto-js';
  123. import encHex from 'crypto-js/enc-hex'
  124. import { createJob, putQueueJob, getKnowledgeBase, getKnowledgeBaseFilesList } from '@/api/AgentApi'
  125. import axios from 'axios'
  126. import { genFileId, ElMessage } from 'element-plus'
  127. import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
  128. import { throttle, debounce } from 'lodash'
  129. const KBTableRef = ref()
  130. const formRef = ref()
  131. const dialogFormVisible = ref(false)
  132. const formLabelWidth = ref('120px')
  133. const paginationData = ref<any>({
  134. currentPage: 1,
  135. pageSizes: [10, 50, 100, 200],
  136. disabled: false,
  137. total: 0,
  138. defaultPageSize: 10,
  139. currentPageSize: 10
  140. })
  141. const totalPage = computed(() => {
  142. return Math.ceil(paginationData.value.total / paginationData.value.currentPageSize)
  143. })
  144. const handleSizeChange = (pageSize: number) => {
  145. paginationData.value.currentPageSize = pageSize
  146. debounceGetKBfileList()
  147. }
  148. const handleCurrentChange = (page: number) => {
  149. paginationData.value.currentPage = page
  150. debounceGetKBfileList()
  151. }
  152. type kbData = {
  153. id: number,
  154. name: string | null,
  155. description: string | null,
  156. tags: string | null,
  157. file_count: number,
  158. created_at: string | null,
  159. updated_at: string | null
  160. }
  161. type knowledgeBaseType = {
  162. visible: boolean,
  163. activeId: number | string,
  164. querySearch: string,
  165. data: kbData[],
  166. filesList: any[]
  167. }
  168. let knowledgeBase = ref<knowledgeBaseType>({
  169. visible: false,
  170. activeId: 0,
  171. querySearch: "",
  172. data: [],
  173. filesList: []
  174. })
  175. function handleKnowledgeBaseClick(data: kbData) {
  176. knowledgeBase.value.activeId = data.id
  177. paginationData.value.currentPage = 1
  178. }
  179. function handleGetKBfileList() {
  180. getKnowledgeBaseFilesList({
  181. file_name: knowledgeBase.value.querySearch,
  182. pageSize: paginationData.value.currentPageSize,
  183. pageNo: paginationData.value.currentPage,
  184. kbId: knowledgeBase.value.activeId
  185. }).then((res: any) => {
  186. const { data, code, message } = res
  187. if (code === 200) {
  188. knowledgeBase.value.filesList = data.list
  189. for (let i = 0; i < knowledgeBase.value.filesList.length; i++) {
  190. knowledgeBase.value.filesList[i].index = (paginationData.value.currentPage - 1) * paginationData.value.currentPageSize + i + 1
  191. knowledgeBase.value.filesList[i].isValid = true
  192. checkLinkValidity(i, knowledgeBase.value.filesList[i].minio_url)
  193. }
  194. paginationData.value.total = data.total
  195. }
  196. }).catch((e: any) => {
  197. console.log(e)
  198. })
  199. }
  200. const throttleGetKBfileList = throttle(handleGetKBfileList, 1000)
  201. const debounceGetKBfileList = debounce(handleGetKBfileList, 500, { leading: true })
  202. const upload = ref()
  203. const form = ref({
  204. job_id: 0,
  205. job_category: '',
  206. job_name: '',
  207. job_details: '',
  208. job_creator: '',
  209. queue_category: '',
  210. queue_name: '',
  211. name: '',
  212. status: 'queue'
  213. })
  214. const fileList = ref<any>([])
  215. const props = defineProps({
  216. title: { type: String, required: true, default: '工作' },
  217. queue_category: { type: String, required: true, default: 'SYSTEM' },
  218. queue_name: { type: String, required: true, default: 'DEFAULT' },
  219. job_category: { type: String, required: false, default: 'USER' },
  220. job_name: { type: String, required: false, default: '工作名称' },
  221. job_details: { type: String, required: false, default: '' },
  222. job_creator: { type: String, required: false, default: 'admin' },
  223. status: { type: Number, required: false, default: 0 },
  224. })
  225. const emit = defineEmits(['update:modelValue', 'success', 'cancel'])
  226. const showDialog = (visible: boolean = true) => {
  227. // console.log("OCRDialog showDialog")
  228. if (upload.value) {
  229. upload.value.clearFiles();
  230. }
  231. dialogFormVisible.value = visible
  232. }
  233. function handleGetKnowledgeBase() {
  234. getKnowledgeBase().then((response: any) => {
  235. // console.log('getKnowledgeBase', response)
  236. const { data, code, message } = response
  237. if (code === 200) {
  238. knowledgeBase.value.data = data.list || []
  239. }
  240. }).catch((e: any) => {
  241. console.log(e)
  242. })
  243. }
  244. function fetchFile(fileName: string, fileUrl: string) {
  245. axios.get(fileUrl, { responseType: 'blob' })
  246. .then(async response => {
  247. // upload.value!.clearFiles()
  248. // 创建文件对象并添加到文件列表
  249. const rawFile = new File([response.data], fileName, { type: response.data.type });
  250. const file = rawFile as UploadRawFile
  251. file.uid = genFileId()
  252. // const fileHashHex = await calculateFileHashForCryptoJS(file)
  253. // const { md5, sha256 } = await hashFile(file)
  254. let fileExist = false;
  255. // await Promise.allSettled(fileList.value.map(async (it: any) => {
  256. // const itFileHash = await hashFile(it.raw);
  257. // if (sha256 === itFileHash.sha256) {
  258. // fileExist = true;
  259. // return true;
  260. // }
  261. // return false;
  262. // }))
  263. if (fileExist) {
  264. ElMessage({
  265. message: `文件“${fileName}”已存在上传列表中`,
  266. type: 'info',
  267. plain: true,
  268. })
  269. } else {
  270. upload.value!.handleStart(file)
  271. ElMessage({
  272. message: `文件“${fileName}”导入成功`,
  273. type: 'success',
  274. plain: true,
  275. })
  276. }
  277. // knowledgeBase.value.visible = false
  278. })
  279. .catch(error => {
  280. ElMessage({
  281. message: `从知识库获取文件“${fileName}”失败`,
  282. type: 'error'
  283. })
  284. console.log(error);
  285. });
  286. }
  287. function handleSelectedImport() {
  288. const SelectionRows = KBTableRef.value.getSelectionRows()
  289. handleImportFiles(toRaw(SelectionRows))
  290. KBTableRef.value.clearSelection()
  291. // console.log(SelectionRows)
  292. }
  293. // 使用原生方法计算文件的hash值,该方法兼容性较差
  294. async function calculateFileHashForNative(file: File) {
  295. const arrayBuffer = await file.arrayBuffer(); // 将文件读取为 ArrayBuffer
  296. const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); // 计算哈希值
  297. const hashArray = Array.from(new Uint8Array(hashBuffer)); // 将哈希转换为字节数组
  298. const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); // 转换为十六进制字符串
  299. // console.log("calculateFileHashForNative1:", hashHex)
  300. return hashHex;
  301. }
  302. // 使用crypto-js计算文件的hash值 ,该方法易发生卡顿
  303. async function calculateFileHashForCryptoJS(file: File) {
  304. return new Promise((resolve, reject) => {
  305. try {
  306. const reader = new FileReader();
  307. reader.readAsArrayBuffer(file)
  308. reader.onload = function (e) {
  309. const arrayBuffer = e.target?.result; // 读取的文件内容
  310. // 将 ArrayBuffer 转换为 CryptoJS 可以处理的 WordArray
  311. const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
  312. // 计算文件的 SHA-256 哈希值并转为 Base64 编码字符串
  313. const hashBase64 = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Base64);
  314. // 计算文件的 SHA-256 哈希值并转为 16 进制字符串
  315. const hashHex = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.hex);
  316. // console.log('calculateFileHashForCryptoJS:', hashHex);
  317. resolve(hashHex)
  318. };
  319. reader.onerror = () => {
  320. reject('')
  321. }
  322. } catch (e) {
  323. console.log(e)
  324. reject('')
  325. }
  326. })
  327. }
  328. function handleImportFiles(filesList: any[]) {
  329. for (let i = 0; i < filesList.length; i++) {
  330. // if (i > 0) break;
  331. fetchFile(filesList[i].file_name, filesList[i].minio_url)
  332. }
  333. knowledgeBase.value.visible = false
  334. }
  335. /**
  336. * 用于计算文件的hash值,包括sha256值和md5值
  337. */
  338. function hashFile(file: any) {
  339. /**
  340. * 使用指定的算法计算hash值
  341. */
  342. function hashFileInternal(file: any, alog: any) {
  343. // 指定块的大小,这里设置为20MB,可以根据实际情况进行配置
  344. const chunkSize = 20 * 1024 * 1024
  345. let promise: any = Promise.resolve()
  346. // 使用promise来串联hash计算的顺序。因为FileReader是在事件中处理文件内容的,必须要通过某种机制来保证update的顺序是文件正确的顺序
  347. for (let index = 0; index < file.size; index += chunkSize) {
  348. promise = promise.then(() => hashBlob(file.slice(index, index + chunkSize)))
  349. }
  350. /**
  351. * 更新文件块的hash值
  352. */
  353. function hashBlob(blob: any) {
  354. return new Promise((resolve, reject) => {
  355. const reader = new FileReader()
  356. reader.onload = ({ target }) => {
  357. const wordArray = CryptoJS.lib.WordArray.create(target?.result)
  358. // 增量更新计算结果
  359. alog.update(wordArray)
  360. resolve(true)
  361. }
  362. reader.readAsArrayBuffer(blob)
  363. })
  364. }
  365. // 使用promise返回最终的计算结果
  366. return promise.then(() => encHex.stringify(alog.finalize()))
  367. }
  368. // 同时计算文件的sha256和md5,并使用promise返回
  369. return Promise.all([hashFileInternal(file, CryptoJS.algo.SHA256.create()),
  370. hashFileInternal(file, CryptoJS.algo.MD5.create())])
  371. .then(([sha256, md5]) => ({
  372. sha256,
  373. md5
  374. }))
  375. }
  376. const handlePreview = (file: any) => {
  377. console.log(file)
  378. }
  379. const handleExceed: UploadProps['onExceed'] = (files) => {
  380. upload.value!.clearFiles()
  381. const file = files[0] as UploadRawFile
  382. // console.log(handleExceed, file)
  383. file.uid = genFileId()
  384. upload.value!.handleStart(file)
  385. }
  386. const handleRemove = (file: any, fileList: any) => {
  387. console.log(file, fileList)
  388. }
  389. const handleSuccess = (response: any, file: any, fileList: any) => {
  390. console.log(response, file, fileList)
  391. emit('success')
  392. }
  393. const handleConfirm = () => {
  394. formRef.value.validate((valid: any) => {
  395. if (valid) {
  396. // console.log('submit', props)
  397. form.value.job_category = props.queue_category + "_" + props.queue_name;
  398. form.value.job_name = form.value.name;
  399. form.value.queue_category = props.queue_category;
  400. form.value.queue_name = props.queue_name;
  401. form.value.job_details = props.job_details;
  402. form.value.job_creator = props.job_creator;
  403. // console.log('form values ', form.value)
  404. createJob(form.value).then((res: any) => {
  405. var job = res.records[0]
  406. form.value.job_id = job.id;
  407. upload.value.submit();
  408. })
  409. }
  410. })
  411. }
  412. defineExpose({ showDialog })
  413. function handleClosed() {
  414. knowledgeBase.value.visible = false
  415. }
  416. watch(() => knowledgeBase.value.activeId, (newValue) => {
  417. debounceGetKBfileList()
  418. // console.log('activeId', newValue)
  419. })
  420. async function checkLinkValidity(index: number, url: string) {
  421. try {
  422. const response = await axios.head(url); // 使用 HEAD 请求,仅请求响应头
  423. if (response.status >= 200 && response.status < 300) {
  424. knowledgeBase.value.filesList[index].isValid = true
  425. } else {
  426. knowledgeBase.value.filesList[index].isValid = false // 返回 false 表示链接无效
  427. }
  428. } catch (error) {
  429. knowledgeBase.value.filesList[index].isValid = false // 请求失败,返回 false
  430. }
  431. }
  432. onMounted(() => {
  433. handleGetKnowledgeBase()
  434. })
  435. </script>
  436. <style lang="less" scoped>
  437. .knowledge-base {
  438. position: fixed;
  439. top: 50%;
  440. left: 50%;
  441. width: 80vw;
  442. height: 90vh;
  443. background-color: white;
  444. display: flex;
  445. flex-direction: column;
  446. // overflow: auto;
  447. transform: translate(-50%, -50%);
  448. border-radius: 5px;
  449. min-width: 800px;
  450. .topbar {
  451. padding: 5px;
  452. flex: 0 0 auto;
  453. .close-knowledge-base {
  454. margin-left: calc(100% - 24px);
  455. cursor: pointer;
  456. }
  457. }
  458. .knowledge-base-content {
  459. flex: 1 1 auto;
  460. // overflow: auto;
  461. display: flex;
  462. flex-wrap: nowrap;
  463. min-height: 0;
  464. .knowledge-base-list {
  465. flex: 0 0 auto;
  466. width: 300px;
  467. // background-color: skyblue;
  468. border-right: 1px solid #E4E4E4;
  469. .knowledge-base-item {
  470. display: flex;
  471. border-bottom: 1px solid #FAFAFA;
  472. padding: 10px 5px;
  473. gap: 0px 20px;
  474. align-items: center;
  475. &:first-child {
  476. background-color: #F2F5F9;
  477. padding: 10px 5px;
  478. // font-size: 17px;
  479. }
  480. &:hover,
  481. &:is(.active) {
  482. background-color: #F2F5F9;
  483. }
  484. .name {
  485. flex: 1 1 auto;
  486. display: flex;
  487. flex-wrap: nowrap;
  488. align-items: center;
  489. min-width: 0px;
  490. .icon {
  491. flex: 0 0 auto;
  492. padding: 2px;
  493. background-color: #7733FF;
  494. border-radius: 50%;
  495. display: inline-block;
  496. margin-right: 10px;
  497. }
  498. .text {
  499. flex: 1 1 auto;
  500. white-space: nowrap;
  501. text-overflow: ellipsis;
  502. overflow: hidden;
  503. width: 100%;
  504. display: flex;
  505. flex-direction: column;
  506. justify-content: center;
  507. &>div {
  508. width: 100%;
  509. white-space: nowrap;
  510. text-overflow: ellipsis;
  511. overflow: hidden;
  512. display: inline-block;
  513. line-height: 1.4;
  514. }
  515. .name-text {
  516. font-weight: bold;
  517. font-size: 16px;
  518. }
  519. .description-text {
  520. font-size: 14px;
  521. color: #D8D8D8;
  522. }
  523. }
  524. }
  525. .number {
  526. flex: 0 0 70px;
  527. }
  528. }
  529. }
  530. .knowledge-base-detail {
  531. flex: 1 1 auto;
  532. min-width: 0px;
  533. overflow: auto;
  534. min-width: 400px;
  535. // background-color: orange;
  536. }
  537. }
  538. .management-content {
  539. flex: 1 1 auto;
  540. min-width: 0px; //让盒子可以压缩
  541. min-height: 0px;
  542. padding: 0px 5px;
  543. display: flex;
  544. height: 100%;
  545. flex-direction: column;
  546. .management-content-top {
  547. clear: both;
  548. flex: 0 0 auto;
  549. position: relative;
  550. .add-file {
  551. position: absolute;
  552. right: 10px;
  553. bottom: 0px;
  554. transform: translateY(-25%);
  555. color: white;
  556. // width: 100px;
  557. background-color: #169BD5;
  558. margin-left: auto;
  559. padding: 10px 25px;
  560. border-radius: 3px;
  561. display: inline-block;
  562. cursor: pointer;
  563. // float: right;
  564. // display: flex;
  565. // .el-icon,
  566. .text {
  567. // vertical-align: middle;
  568. // margin-left: 10px;
  569. }
  570. }
  571. }
  572. .management-content-middle {
  573. flex: 1 1 auto;
  574. // overflow: auto;
  575. padding-right: 10px;
  576. /deep/ .el-scrollbar {
  577. .el-table__header-wrapper {
  578. position: sticky;
  579. top: 0;
  580. }
  581. }
  582. }
  583. .pagination {
  584. flex: 0 0 auto;
  585. }
  586. }
  587. &::v-deep .el-table {
  588. min-width: 0px;
  589. max-width: 100%;
  590. .document-download-icon {
  591. width: 28px;
  592. height: 28px;
  593. cursor: pointer;
  594. }
  595. .list-name {
  596. white-space: nowrap;
  597. text-overflow: ellipsis;
  598. display: inline;
  599. }
  600. .circle {
  601. border-radius: 50%;
  602. width: 10px;
  603. height: 10px;
  604. background-color: #8AD068;
  605. display: inline-block;
  606. margin-right: 10px;
  607. &:is([type='warning']) {
  608. background-color: #E6A23C;
  609. }
  610. }
  611. .operation {
  612. display: flex;
  613. align-items: center;
  614. justify-content: space-evenly;
  615. .el-switch {
  616. &:is(.is-checked) {
  617. .el-switch__core {
  618. background-color: #155AEF;
  619. }
  620. }
  621. .el-switch__core {
  622. border-radius: 5px;
  623. width: 35px;
  624. min-width: 0px;
  625. // background-color: #155AEF;
  626. }
  627. .el-switch__action {
  628. border-radius: 3px;
  629. }
  630. }
  631. }
  632. }
  633. .search {
  634. // padding: 10px 20px;
  635. margin: 5px;
  636. background: white;
  637. display: inline-block;
  638. /deep/ .el-input__wrapper {
  639. background-color: #EFF1F5;
  640. border-radius: 10px;
  641. }
  642. .el-input {
  643. width: 250px;
  644. }
  645. }
  646. .pagination {
  647. // text-align: right;
  648. // background-color: @bgColor;
  649. display: flex;
  650. padding: 0px 5px;
  651. /deep/.el-pagination {
  652. background-color: #fff;
  653. // justify-content: end;
  654. margin-left: auto;
  655. padding: 10px 0px 20px;
  656. // margin: 0px 20px;
  657. .page-box {
  658. border: 1px solid #E7E8EE;
  659. border-radius: 3px;
  660. box-sizing: border-box;
  661. }
  662. .el-pager {
  663. .is-active {
  664. color: #fff;
  665. background-color: #5780FC;
  666. }
  667. .number {
  668. .page-box();
  669. margin-left: 10px;
  670. &:last-child {
  671. margin-right: 10px;
  672. }
  673. }
  674. }
  675. .btn-next,
  676. .btn-quicknext,
  677. .btn-quickprev,
  678. .btn-prev {
  679. .page-box()
  680. }
  681. .btn-quicknext,
  682. .btn-quickprev {
  683. margin-left: 10px;
  684. }
  685. }
  686. }
  687. .document-icon {
  688. width: 32px;
  689. height: 32px;
  690. }
  691. }
  692. </style>