Admin.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. <template>
  2. <div class="admin-container">
  3. <!-- 左侧边栏 -->
  4. <div class="sidebar">
  5. <!-- 顶部AI图标 -->
  6. <div class="sidebar-header">
  7. <img src="./assets/admin/1.png" alt="AI" class="ai-icon">
  8. <span class="ai-title">AI后台管理系统</span>
  9. </div>
  10. <!-- 导航菜单 -->
  11. <nav class="sidebar-nav">
  12. <div
  13. :class="['nav-item', { active: currentPage === 'chat' }]"
  14. @click="switchPage('chat')"
  15. >
  16. <img src="./assets/admin/2.png" alt="对话记录" class="nav-icon">
  17. <span class="nav-text">AI对话记录</span>
  18. </div>
  19. <div
  20. :class="['nav-item', { active: currentPage === 'data' }]"
  21. @click="switchPage('data')"
  22. >
  23. <img src="./assets/admin/3.png" alt="数据资源" class="nav-icon">
  24. <span class="nav-text">数据资源</span>
  25. </div>
  26. </nav>
  27. <!-- 底部用户信息 -->
  28. <div class="sidebar-footer">
  29. <div class="user-info">
  30. <img src="./assets/admin/4.png" alt="管理员" class="user-avatar">
  31. <div class="user-details">
  32. <span class="user-name">管理员</span>
  33. <span class="user-role">超级管理员</span>
  34. </div>
  35. </div>
  36. <button class="logout-btn" @click="handleLogout">
  37. <img src="./assets/admin/5.png" alt="退出" class="logout-icon">
  38. </button>
  39. </div>
  40. </div>
  41. <!-- 右侧表单区域 -->
  42. <div class="main-content1">
  43. <!-- AI对话记录页面 -->
  44. <div v-if="currentPage === 'chat'">
  45. <!-- 顶部标题和搜索 -->
  46. <div class="content-header">
  47. <div class="header-left">
  48. <h1 class="page-title">AI对话记录</h1>
  49. <p class="page-description">查看和管理所有用户与AI助手的对话历史记录</p>
  50. </div>
  51. <div class="search-box">
  52. <img src="./assets/admin/6.png" alt="搜索" class="search-icon">
  53. <input
  54. type="text"
  55. placeholder="搜索用户ID或问题内容"
  56. class="search-input"
  57. v-model="searchKeyword"
  58. >
  59. </div>
  60. </div>
  61. <!-- 数据表格 -->
  62. <div class="data-table">
  63. <table class="table">
  64. <thead>
  65. <tr>
  66. <th>序号</th>
  67. <th>用户ID</th>
  68. <th>提问内容</th>
  69. <th>回答内容</th>
  70. <th>提问时间</th>
  71. <th>状态</th>
  72. </tr>
  73. </thead>
  74. <tbody>
  75. <tr v-for="(record, index) in filteredRecords" :key="index">
  76. <td>{{ index + 1 }}</td>
  77. <td>{{ record.userId }}</td>
  78. <td class="content-cell">
  79. <div class="content-preview">{{ record.userMessage }}</div>
  80. </td>
  81. <td class="content-cell">
  82. <div class="content-preview">{{ record.aiReply }}</div>
  83. </td>
  84. <td>{{ record.timestamp }}</td>
  85. <td>
  86. <span :class="['status-badge', record.status]">
  87. {{ getStatusText(record.status) }}
  88. </span>
  89. </td>
  90. </tr>
  91. </tbody>
  92. </table>
  93. </div>
  94. <!-- 分页器 -->
  95. <div class="pagination">
  96. <button class="page-btn prev-btn" @click="prevPage">
  97. <span>‹</span>
  98. </button>
  99. <div class="page-numbers">
  100. <span
  101. v-for="page in visiblePages"
  102. :key="page"
  103. :class="['page-number', { active: page === chatCurrentPage }]"
  104. @click="goToPage(page)"
  105. >
  106. {{ page }}
  107. </span>
  108. </div>
  109. <button class="page-btn next-btn" @click="nextPage">
  110. <span>›</span>
  111. </button>
  112. <div class="page-info">
  113. 第{{ chatCurrentPage }}页,共{{ totalPages }}页
  114. </div>
  115. </div>
  116. </div>
  117. <!-- 数据资源页面 -->
  118. <div v-if="currentPage === 'data'">
  119. <!-- 顶部标题和搜索 -->
  120. <div class="content-header">
  121. <div class="header-left">
  122. <h1 class="page-title">数据资源</h1>
  123. <p class="page-description">查看和管理已标注的相关文档知识点</p>
  124. </div>
  125. <div class="search-box">
  126. <img src="./assets/admin/6.png" alt="搜索" class="search-icon">
  127. <input
  128. type="text"
  129. placeholder="搜索ID、知识点内容或标签"
  130. class="search-input"
  131. v-model="dataSearchKeyword"
  132. >
  133. </div>
  134. </div>
  135. <!-- 数据表格 -->
  136. <div class="data-table">
  137. <table class="table">
  138. <thead>
  139. <tr>
  140. <th>ID</th>
  141. <th>内容</th>
  142. <th>标签</th>
  143. <th>录入时间</th>
  144. <th>来源</th>
  145. </tr>
  146. </thead>
  147. <tbody>
  148. <tr v-for="(item, index) in filteredDataItems" :key="index">
  149. <td>{{ item.id }}</td>
  150. <td class="content-cell">
  151. <div class="content-preview">{{ item.content }}</div>
  152. </td>
  153. <td>
  154. <div class="tags-container">
  155. <span
  156. v-for="tag in item.tags"
  157. :key="tag"
  158. class="tag"
  159. >
  160. {{ tag }}
  161. </span>
  162. </div>
  163. </td>
  164. <td>{{ item.timestamp }}</td>
  165. <td class="content-cell">
  166. <div class="content-preview">{{ item.source }}</div>
  167. </td>
  168. </tr>
  169. </tbody>
  170. </table>
  171. </div>
  172. <!-- 分页器 -->
  173. <div class="pagination">
  174. <button class="page-btn prev-btn" @click="prevDataPage">
  175. <span>‹</span>
  176. </button>
  177. <div class="page-numbers">
  178. <span
  179. v-for="page in dataVisiblePages"
  180. :key="page"
  181. :class="['page-number', { active: page === dataCurrentPage }]"
  182. @click="goToDataPage(page)"
  183. >
  184. {{ page }}
  185. </span>
  186. </div>
  187. <button class="page-btn next-btn" @click="nextDataPage">
  188. <span>›</span>
  189. </button>
  190. <div class="page-info">
  191. 第{{ dataCurrentPage }}页,共{{ dataTotalPages }}页
  192. </div>
  193. </div>
  194. </div>
  195. </div>
  196. </div>
  197. </template>
  198. <script setup>
  199. import { ref, computed, onMounted } from 'vue'
  200. // 响应式数据
  201. const currentPage = ref('chat') // 当前页面:'chat' 或 'data'
  202. const searchKeyword = ref('')
  203. const chatCurrentPage = ref(1)
  204. const pageSize = ref(15)
  205. // 数据资源相关
  206. const dataSearchKeyword = ref('')
  207. const dataCurrentPage = ref(1)
  208. const dataPageSize = ref(15)
  209. // 模拟数据
  210. const records = ref([
  211. {
  212. userId: 'SD00125',
  213. userMessage: '施工现场照片如何识别脚手架搭设隐患?',
  214. aiReply: '脚手架搭设隐患识别可以通过以下几个关键点:1.检查连接件是否松动;2.立杆垂直度是否满足要求;3.横杆间距是否符合规范...',
  215. timestamp: '2023-10-15 14:32:45',
  216. status: 'liked'
  217. },
  218. {
  219. userId: 'SD00078',
  220. userMessage: '高性能混凝土在桥梁工程中的应用特点?',
  221. aiReply: '高性能混凝土在桥梁工程中的主要应用特点包括:1.具有更高的强度和耐久性;2.抗渗性能优异;3.施工性能良好...',
  222. timestamp: '2023-10-14 11:03:51',
  223. status: 'liked'
  224. },
  225. {
  226. userId: 'SD00234',
  227. userMessage: '桥梁健康监测系统如何设计?',
  228. aiReply: '桥梁健康监测系统设计需要考虑:1.传感器布置优化;2.数据采集与传输;3.健康状态评估算法;4.预警机制建立...',
  229. timestamp: '2023-10-13 16:45:22',
  230. status: 'liked'
  231. },
  232. {
  233. userId: 'SD00156',
  234. userMessage: '桥梁施工质量控制要点有哪些?',
  235. aiReply: '桥梁施工质量控制要点包括:1.原材料质量控制;2.施工工艺控制;3.质量检测与验收;4.质量记录管理...',
  236. timestamp: '2023-10-12 09:15:33',
  237. status: 'liked'
  238. },
  239. {
  240. userId: 'SD00345',
  241. userMessage: '桥梁抗震设计规范要求?',
  242. aiReply: '桥梁抗震设计规范要求:1.地震作用计算;2.结构抗震验算;3.构造措施要求;4.抗震性能目标...',
  243. timestamp: '2023-10-11 14:28:17',
  244. status: 'disliked'
  245. },
  246. {
  247. userId: 'SD00189',
  248. userMessage: '桥梁养护管理策略?',
  249. aiReply: '桥梁养护管理策略:1.定期检查制度;2.预防性养护;3.维修加固技术;4.养护资金管理...',
  250. timestamp: '2023-10-10 11:42:55',
  251. status: 'liked'
  252. },
  253. {
  254. userId: 'SD00456',
  255. userMessage: '桥梁施工安全管理措施?',
  256. aiReply: '桥梁施工安全管理措施:1.安全教育培训;2.安全防护设施;3.安全检查制度;4.应急预案制定...',
  257. timestamp: '2023-10-09 15:33:28',
  258. status: 'liked'
  259. },
  260. {
  261. userId: 'SD00278',
  262. userMessage: '桥梁设计荷载标准?',
  263. aiReply: '桥梁设计荷载标准:1.恒载计算;2.活载标准;3.风荷载;4.地震荷载;5.温度荷载...',
  264. timestamp: '2023-10-08 10:25:41',
  265. status: 'liked'
  266. },
  267. {
  268. userId: 'SD00567',
  269. userMessage: '桥梁施工进度控制方法?',
  270. aiReply: '桥梁施工进度控制方法:1.进度计划编制;2.进度监控;3.进度调整;4.进度报告制度...',
  271. timestamp: '2023-10-07 13:18:36',
  272. status: 'liked'
  273. },
  274. {
  275. userId: 'SD00321',
  276. userMessage: '桥梁检测技术发展现状?',
  277. aiReply: '桥梁检测技术发展现状:1.无损检测技术;2.智能检测设备;3.检测数据分析;4.检测标准完善...',
  278. timestamp: '2023-10-06 16:52:19',
  279. status: 'liked'
  280. },
  281. {
  282. userId: 'SD00678',
  283. userMessage: '桥梁施工环境保护要求?',
  284. aiReply: '桥梁施工环境保护要求:1.噪声控制;2.扬尘控制;3.废水处理;4.固体废物管理...',
  285. timestamp: '2023-10-05 09:41:27',
  286. status: 'liked'
  287. },
  288. {
  289. userId: 'SD00432',
  290. userMessage: '桥梁施工成本控制策略?',
  291. aiReply: '桥梁施工成本控制策略:1.成本计划编制;2.成本核算;3.成本分析;4.成本控制措施...',
  292. timestamp: '2023-10-04 14:15:33',
  293. status: 'liked'
  294. },
  295. {
  296. userId: 'SD00789',
  297. userMessage: '桥梁施工技术发展趋势?',
  298. aiReply: '桥梁施工技术发展趋势:1.智能化施工;2.绿色施工;3.装配式施工;4.信息化管理...',
  299. timestamp: '2023-10-03 11:28:45',
  300. status: 'liked'
  301. },
  302. {
  303. userId: 'SD00543',
  304. userMessage: '桥梁施工质量验收标准?',
  305. aiReply: '桥梁施工质量验收标准:1.验收程序;2.验收标准;3.验收方法;4.验收记录...',
  306. timestamp: '2023-10-02 15:37:12',
  307. status: 'liked'
  308. },
  309. {
  310. userId: 'SD00890',
  311. userMessage: '桥梁施工安全风险评估?',
  312. aiReply: '桥梁施工安全风险评估:1.风险识别;2.风险分析;3.风险评价;4.风险控制措施...',
  313. timestamp: '2023-10-01 10:52:38',
  314. status: 'liked'
  315. }
  316. ])
  317. // 数据资源模拟数据
  318. const dataItems = ref([
  319. {
  320. id: 'KB0012',
  321. content: '公路路基不均匀沉降处理方法:1.采用换填法处理软土地基;2.设置沉降缝和伸缩缝;3.加强路基压实度控制;4.采用预压法减少工后沉降。',
  322. tags: ['路基', '公路工程', '沉降处理'],
  323. timestamp: '2023-10-18 14:32:45',
  324. source: '《公路路基施工技术规范》(JTG F10-2006)'
  325. },
  326. {
  327. id: 'KB0007',
  328. content: '高性能混凝土在桥梁工程中的应用特点:1.具有更高的强度和耐久性;2.抗渗性能优异;3.施工性能良好;4.能够满足大跨度桥梁的承载要求。',
  329. tags: ['混凝土', '桥梁工程', '材料应用'],
  330. timestamp: '2023-10-17 11:03:51',
  331. source: '《公路桥涵施工技术规范》(JTG/T F50-2011)'
  332. },
  333. {
  334. id: 'KB0034',
  335. content: '大跨径桥梁施工技术要点:1.采用悬臂浇筑法施工;2.设置临时支撑体系;3.实施智能监测系统;4.严格控制施工精度和质量。',
  336. tags: ['大跨径桥梁', '施工技术', '智能监测'],
  337. timestamp: '2023-10-16 16:45:22',
  338. source: '《公路工程技术标准》(JTG B01-2014)'
  339. },
  340. {
  341. id: 'KB0015',
  342. content: '公路车道宽度设计标准:1.一级公路车道宽度为3.75m;2.二级公路车道宽度为3.5m;3.三级公路车道宽度为3.25m;4.四级公路车道宽度为3.0m。',
  343. tags: ['公路标准', '车道宽度', '设计规范'],
  344. timestamp: '2023-10-15 09:15:33',
  345. source: '《公路工程技术标准》(JTG B01-2014)'
  346. },
  347. {
  348. id: 'KB0029',
  349. content: '桥梁预应力张拉施工注意事项:1.张拉前检查锚具和钢绞线;2.控制张拉力和伸长量;3.实施对称张拉;4.做好张拉记录和检测。',
  350. tags: ['预应力', '桥梁施工', '张拉技术'],
  351. timestamp: '2023-10-14 14:28:17',
  352. source: '《公路桥涵施工技术规范》(JTG/T F50-2011)'
  353. },
  354. {
  355. id: 'KB0018',
  356. content: 'SMA与SUP沥青混合料区别:1.SMA具有更好的抗车辙性能;2.SUP具有更好的低温性能;3.施工工艺要求不同;4.适用场景有所差异。',
  357. tags: ['沥青混合料', '路面材料', 'SMA'],
  358. timestamp: '2023-10-13 11:42:55',
  359. source: '《公路沥青路面施工技术规范》(JTG F40-2004)'
  360. },
  361. {
  362. id: 'KB0005',
  363. content: '隧道突水突泥防治措施:1.超前地质预报;2.注浆加固围岩;3.设置排水系统;4.加强施工监测和预警。',
  364. tags: ['隧道施工', '安全防护', '突水防治'],
  365. timestamp: '2023-10-12 15:33:28',
  366. source: '《公路隧道施工技术规范》(JTG F60-2009)'
  367. },
  368. {
  369. id: 'KB0021',
  370. content: '脚手架搭设隐患识别要点:1.检查连接件是否松动;2.立杆垂直度是否满足要求;3.横杆间距是否符合规范;4.安全防护设施是否完善。',
  371. tags: ['脚手架', '施工安全', '隐患识别'],
  372. timestamp: '2023-10-11 10:25:41',
  373. source: '《建筑施工安全检查标准》(JGJ 59-2011)'
  374. },
  375. {
  376. id: 'KB0043',
  377. content: '高铁轨道精调技术要点:1.轨道几何状态检测;2.扣件调整和更换;3.轨道平顺性控制;4.精调后质量验收。',
  378. tags: ['高铁轨道', '精调技术', '质量控制'],
  379. timestamp: '2023-10-10 13:18:36',
  380. source: '《高速铁路轨道工程施工质量验收标准》(TB 10754-2010)'
  381. },
  382. {
  383. id: 'KB0038',
  384. content: '桥梁健康监测系统设计原则:1.传感器布置优化;2.数据采集与传输;3.健康状态评估算法;4.预警机制建立。',
  385. tags: ['桥梁监测', '健康评估', '智能系统'],
  386. timestamp: '2023-10-09 16:52:19',
  387. source: '《公路桥梁技术状况评定标准》(JTG/T H21-2011)'
  388. },
  389. {
  390. id: 'KB0025',
  391. content: '公路施工环境保护要求:1.噪声控制措施;2.扬尘控制技术;3.废水处理工艺;4.固体废物管理。',
  392. tags: ['环境保护', '施工管理', '绿色施工'],
  393. timestamp: '2023-10-08 09:41:27',
  394. source: '《公路工程施工环境保护技术规范》(JTG B06-2007)'
  395. },
  396. {
  397. id: 'KB0041',
  398. content: '桥梁抗震设计要点:1.地震作用计算;2.结构抗震验算;3.构造措施要求;4.抗震性能目标。',
  399. tags: ['桥梁抗震', '结构设计', '安全性能'],
  400. timestamp: '2023-10-07 14:15:33',
  401. source: '《公路桥梁抗震设计细则》(JTG/T B02-01-2008)'
  402. }
  403. ])
  404. // 计算属性
  405. const filteredRecords = computed(() => {
  406. if (!searchKeyword.value) return records.value
  407. return records.value.filter(record =>
  408. record.userId.includes(searchKeyword.value) ||
  409. record.userMessage.includes(searchKeyword.value) ||
  410. record.aiReply.includes(searchKeyword.value)
  411. )
  412. })
  413. const totalRecords = computed(() => filteredRecords.value.length)
  414. const totalPages = computed(() => Math.ceil(totalRecords.value / pageSize.value))
  415. const visiblePages = computed(() => {
  416. const pages = []
  417. const start = Math.max(1, chatCurrentPage.value - 2)
  418. const end = Math.min(totalPages.value, start + 4)
  419. for (let i = start; i <= end; i++) {
  420. pages.push(i)
  421. }
  422. return pages
  423. })
  424. // 数据资源计算属性
  425. const filteredDataItems = computed(() => {
  426. if (!dataSearchKeyword.value) return dataItems.value
  427. return dataItems.value.filter(item =>
  428. item.id.includes(dataSearchKeyword.value) ||
  429. item.content.includes(dataSearchKeyword.value) ||
  430. item.tags.some(tag => tag.includes(dataSearchKeyword.value)) ||
  431. item.source.includes(dataSearchKeyword.value)
  432. )
  433. })
  434. const dataTotalRecords = computed(() => filteredDataItems.value.length)
  435. const dataTotalPages = computed(() => Math.ceil(dataTotalRecords.value / dataPageSize.value))
  436. const dataVisiblePages = computed(() => {
  437. const pages = []
  438. const start = Math.max(1, dataCurrentPage.value - 2)
  439. const end = Math.min(dataTotalPages.value, start + 4)
  440. for (let i = start; i <= end; i++) {
  441. pages.push(i)
  442. }
  443. return pages
  444. })
  445. // 方法
  446. const switchPage = (page) => {
  447. currentPage.value = page
  448. }
  449. const getStatusText = (status) => {
  450. const statusMap = {
  451. 'liked': '已点赞',
  452. 'disliked': '踩',
  453. 'empty': ''
  454. }
  455. return statusMap[status] || ''
  456. }
  457. // AI对话记录分页方法
  458. const prevPage = () => {
  459. if (chatCurrentPage.value > 1) {
  460. chatCurrentPage.value--
  461. }
  462. }
  463. const nextPage = () => {
  464. if (chatCurrentPage.value < totalPages.value) {
  465. chatCurrentPage.value++
  466. }
  467. }
  468. const goToPage = (page) => {
  469. chatCurrentPage.value = page
  470. }
  471. // 数据资源分页方法
  472. const prevDataPage = () => {
  473. if (dataCurrentPage.value > 1) {
  474. dataCurrentPage.value--
  475. }
  476. }
  477. const nextDataPage = () => {
  478. if (dataCurrentPage.value < dataTotalPages.value) {
  479. dataCurrentPage.value++
  480. }
  481. }
  482. const goToDataPage = (page) => {
  483. dataCurrentPage.value = page
  484. }
  485. // 退出登录
  486. const handleLogout = () => {
  487. // 清除所有token和用户信息
  488. localStorage.removeItem('shudao_refresh_token')
  489. localStorage.removeItem('shudao_token_type')
  490. localStorage.removeItem('shudao_username')
  491. // 重定向到统一认证门户
  492. window.location.href = 'https://tyrz.scgsdsj.com/iga/login_sd.html'
  493. console.log('🚪 已退出登录,跳转到统一认证门户')
  494. }
  495. onMounted(() => {
  496. // 初始化数据
  497. })
  498. </script>
  499. <style lang="less" scoped>
  500. .admin-container {
  501. display: flex;
  502. height: 100vh;
  503. font-family: 'Alibaba PuHuiTi 3.0', sans-serif;
  504. }
  505. /* 左侧边栏 */
  506. .sidebar {
  507. width: 240px;
  508. background: url('./assets/admin/7.png') no-repeat;
  509. // background-size: 240px 100%;
  510. background-position: center;
  511. display: flex;
  512. flex-direction: column;
  513. color: white;
  514. .sidebar-header {
  515. padding: 24px 20px;
  516. border-bottom: 1px solid #DBEAFE50;
  517. display: flex;
  518. align-items: center;
  519. gap: 12px;
  520. .ai-icon {
  521. width: 40px;
  522. height: 40px;
  523. object-fit: contain;
  524. }
  525. .ai-title {
  526. font-size: 18px;
  527. font-weight: 600;
  528. color: #fff;
  529. }
  530. }
  531. .sidebar-nav {
  532. flex: 1;
  533. padding: 20px 0;
  534. .nav-item {
  535. display: flex;
  536. align-items: center;
  537. gap: 12px;
  538. padding: 16px 16px;
  539. cursor: pointer;
  540. transition: all 0.3s ease;
  541. &:hover {
  542. background: rgba(255, 255, 255, 0.1);
  543. }
  544. &.active {
  545. background: #3E7BFA;
  546. border-left: 4px solid #fff;
  547. }
  548. .nav-icon {
  549. width: 20px;
  550. height: 20px;
  551. object-fit: contain;
  552. }
  553. .nav-text {
  554. font-size: 14px;
  555. color: #fff;
  556. }
  557. }
  558. }
  559. .sidebar-footer {
  560. padding: 20px;
  561. border-top: 1px solid #333;
  562. display: flex;
  563. justify-content: space-between;
  564. align-items: center;
  565. .user-info {
  566. display: flex;
  567. align-items: center;
  568. gap: 12px;
  569. .user-avatar {
  570. width: 36px;
  571. height: 36px;
  572. object-fit: contain;
  573. border-radius: 50%;
  574. }
  575. .user-details {
  576. display: flex;
  577. flex-direction: column;
  578. gap: 2px;
  579. .user-name {
  580. font-size: 14px;
  581. color: #fff;
  582. font-weight: 500;
  583. }
  584. .user-role {
  585. font-size: 12px;
  586. color: #999;
  587. }
  588. }
  589. }
  590. .logout-btn {
  591. background: none;
  592. border: none;
  593. cursor: pointer;
  594. padding: 8px;
  595. border-radius: 4px;
  596. transition: all 0.3s ease;
  597. &:hover {
  598. background: rgba(255, 255, 255, 0.1);
  599. }
  600. .logout-icon {
  601. width: 18px;
  602. height: 18px;
  603. object-fit: contain;
  604. }
  605. }
  606. }
  607. }
  608. /* 右侧主内容区 */
  609. .main-content1 {
  610. flex: 1;
  611. // background: #f5f5f5;
  612. display: flex;
  613. flex-direction: column;
  614. overflow: hidden;
  615. }
  616. /* 内容头部 */
  617. .content-header {
  618. // background: white;
  619. padding: 24px 32px;
  620. // border-bottom: 1px solid #e5e5e5;
  621. display: flex;
  622. justify-content: space-between;
  623. align-items: flex-start;
  624. .header-left {
  625. .page-title {
  626. font-size: 24px;
  627. font-weight: 600;
  628. color: #333;
  629. margin: 0 0 8px 0;
  630. }
  631. .page-description {
  632. font-size: 14px;
  633. color: #666;
  634. margin: 0;
  635. }
  636. }
  637. .search-box {
  638. display: flex;
  639. align-items: center;
  640. gap: 12px;
  641. background: #f8f9fa;
  642. border: 1px solid #e5e5e5;
  643. border-radius: 8px;
  644. padding: 12px 16px;
  645. width: 300px;
  646. margin-right: 15px;
  647. margin-top: 8px;
  648. .search-icon {
  649. width: 16px;
  650. height: 16px;
  651. object-fit: contain;
  652. }
  653. .search-input {
  654. flex: 1;
  655. border: none;
  656. background: transparent;
  657. outline: none;
  658. font-size: 14px;
  659. color: #333;
  660. &::placeholder {
  661. color: #999;
  662. }
  663. }
  664. }
  665. }
  666. /* 数据表格 */
  667. .data-table {
  668. flex: 1;
  669. overflow: auto;
  670. padding: 0px 32px;
  671. .table {
  672. width: 100%;
  673. background: white;
  674. border-radius: 8px;
  675. border-collapse: collapse;
  676. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  677. th, td {
  678. padding: 16px;
  679. text-align: left;
  680. border-bottom: 1px solid #f0f0f0;
  681. }
  682. th {
  683. background: #f8f9fa;
  684. font-weight: 600;
  685. color: #333;
  686. font-size: 14px;
  687. }
  688. td {
  689. color: #666;
  690. font-size: 14px;
  691. }
  692. .content-cell {
  693. max-width: 300px;
  694. .content-preview {
  695. max-height: 60px;
  696. overflow: hidden;
  697. text-overflow: ellipsis;
  698. display: -webkit-box;
  699. -webkit-line-clamp: 1;
  700. -webkit-box-orient: vertical;
  701. line-height: 1.4;
  702. }
  703. }
  704. .status-badge {
  705. padding: 4px 12px;
  706. border-radius: 4px;
  707. font-size: 12px;
  708. font-weight: 500;
  709. &.liked {
  710. background: rgba(34, 197, 94, 0.1);
  711. color: #22c55e;
  712. }
  713. &.disliked {
  714. background: #f3f4f6;
  715. color: #6b7280;
  716. }
  717. &.empty {
  718. background: transparent;
  719. color: transparent;
  720. }
  721. }
  722. .tags-container {
  723. display: flex;
  724. flex-wrap: wrap;
  725. gap: 4px;
  726. .tag {
  727. background: rgba(59, 130, 246, 0.1);
  728. color: #3b82f6;
  729. padding: 2px 8px;
  730. border-radius: 4px;
  731. font-size: 12px;
  732. font-weight: 500;
  733. white-space: nowrap;
  734. }
  735. }
  736. }
  737. }
  738. /* 分页器 */
  739. .pagination {
  740. // background: white;
  741. padding: 20px 30px;
  742. // border-top: 1px solid #e5e5e5;
  743. display: flex;
  744. justify-content: center;
  745. align-items: center;
  746. gap: 10px;
  747. .page-btn {
  748. width: 32px;
  749. height: 32px;
  750. border: 1px solid #e5e5e5;
  751. background: white;
  752. color: #666;
  753. border-radius: 4px;
  754. cursor: pointer;
  755. transition: all 0.3s ease;
  756. display: flex;
  757. align-items: center;
  758. justify-content: center;
  759. font-size: 18px;
  760. &:hover:not(:disabled) {
  761. background: #f8f9fa;
  762. border-color: #3E7BFA;
  763. color: #3E7BFA;
  764. }
  765. &:disabled {
  766. opacity: 0.5;
  767. cursor: not-allowed;
  768. }
  769. }
  770. .page-numbers {
  771. display: flex;
  772. gap: 8px;
  773. .page-number {
  774. width: 32px;
  775. height: 32px;
  776. border: 1px solid #e5e5e5;
  777. background: white;
  778. color: #666;
  779. border-radius: 4px;
  780. cursor: pointer;
  781. transition: all 0.3s ease;
  782. display: flex;
  783. align-items: center;
  784. justify-content: center;
  785. font-size: 14px;
  786. &:hover {
  787. background: #f8f9fa;
  788. border-color: #3E7BFA;
  789. color: #3E7BFA;
  790. }
  791. &.active {
  792. background: #3E7BFA;
  793. color: white;
  794. border-color: #3E7BFA;
  795. }
  796. }
  797. }
  798. .page-info {
  799. color: #666;
  800. font-size: 14px;
  801. margin-left: 20px;
  802. }
  803. }
  804. </style>