Foods.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <template>
  2. <div class="foods admin-page">
  3. <el-card shadow="never">
  4. <div slot="header" class="admin-page-header">
  5. <span class="admin-page-title">
  6. <i class="el-icon-apple" style="color: #67c23a; margin-right: 8px;"></i>
  7. 食材管理
  8. </span>
  9. <div class="header-actions">
  10. <el-input
  11. v-model="keyword"
  12. placeholder="搜索食材名称..."
  13. prefix-icon="el-icon-search"
  14. clearable
  15. size="mini"
  16. style="width: 200px; margin-right: 8px;"
  17. @keyup.enter.native="handleSearch"
  18. @clear="handleSearch"
  19. />
  20. <el-select
  21. v-model="categoryFilter"
  22. placeholder="分类筛选"
  23. size="mini"
  24. clearable
  25. style="width: 140px; margin-right: 8px;"
  26. @change="handleSearch"
  27. >
  28. <el-option label="全部分类" value="" />
  29. <el-option v-for="opt in categoryOptions" :key="opt" :label="opt" :value="opt" />
  30. </el-select>
  31. <el-tag
  32. v-if="sortState.length > 0"
  33. closable
  34. size="mini"
  35. class="sort-tag"
  36. @close="clearSort"
  37. @click="clearSort"
  38. >
  39. {{ sortLabel }}
  40. </el-tag>
  41. <el-button type="primary" size="mini" icon="el-icon-plus" @click="openCreate">新建食材</el-button>
  42. </div>
  43. </div>
  44. <el-table
  45. :data="list"
  46. v-loading="loading"
  47. border
  48. stripe
  49. size="mini"
  50. style="width: 100%"
  51. :max-height="tableHeight"
  52. >
  53. <el-table-column type="index" label="#" width="50" align="center" />
  54. <el-table-column prop="name" label="名称" min-width="120" fixed :class-name="sortClass('name')">
  55. <template slot-scope="{ row }">
  56. <span style="font-weight: 500;">{{ row.name }}</span>
  57. </template>
  58. <template slot="header">
  59. <span class="sort-header" @click.stop="onSortClick('name',$event)" @dblclick.stop="onSortToggle('name')">名称<span v-if="sortIndex('name') !== null" class="sort-index" :class="sortClass('name') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}</span>
  60. </template>
  61. </el-table-column>
  62. <el-table-column prop="category" label="分类" width="120" align="center" :class-name="sortClass('category')">
  63. <template slot-scope="{ row }">
  64. <el-tag :type="categoryTagType(row.category)" size="mini" effect="plain">
  65. {{ row.category }}
  66. </el-tag>
  67. </template>
  68. <template slot="header">
  69. <span class="sort-header" @click.stop="onSortClick('category',$event)" @dblclick.stop="onSortToggle('category')">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
  70. </template>
  71. </el-table-column>
  72. <el-table-column label="热量" width="100" align="right" :class-name="sortClass('calories')">
  73. <template slot-scope="{ row }">
  74. <span class="num-val">{{ row.calories != null ? row.calories : '-' }}</span>
  75. <span class="num-unit">kcal</span>
  76. </template>
  77. <template slot="header">
  78. <span class="sort-header" @click.stop="onSortClick('calories',$event)" @dblclick.stop="onSortToggle('calories')">热量<span v-if="sortIndex('calories') !== null" class="sort-index" :class="sortClass('calories') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('calories') }}</span>{{ sortIcon('calories') }}</span>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="蛋白质" width="90" align="right" :class-name="sortClass('protein')">
  82. <template slot-scope="{ row }">
  83. <span class="num-val">{{ row.protein != null ? row.protein : '-' }}</span>
  84. <span class="num-unit">g</span>
  85. </template>
  86. <template slot="header">
  87. <span class="sort-header" @click.stop="onSortClick('protein',$event)" @dblclick.stop="onSortToggle('protein')">蛋白<span v-if="sortIndex('protein') !== null" class="sort-index" :class="sortClass('protein') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('protein') }}</span>{{ sortIcon('protein') }}</span>
  88. </template>
  89. </el-table-column>
  90. <el-table-column label="脂肪" width="80" align="right" :class-name="sortClass('fat')">
  91. <template slot-scope="{ row }">
  92. <span class="num-val">{{ row.fat != null ? row.fat : '-' }}</span>
  93. <span class="num-unit">g</span>
  94. </template>
  95. <template slot="header">
  96. <span class="sort-header" @click.stop="onSortClick('fat',$event)" @dblclick.stop="onSortToggle('fat')">脂肪<span v-if="sortIndex('fat') !== null" class="sort-index" :class="sortClass('fat') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('fat') }}</span>{{ sortIcon('fat') }}</span>
  97. </template>
  98. </el-table-column>
  99. <el-table-column label="碳水" width="80" align="right" :class-name="sortClass('carbs')">
  100. <template slot-scope="{ row }">
  101. <span class="num-val">{{ row.carbs != null ? row.carbs : '-' }}</span>
  102. <span class="num-unit">g</span>
  103. </template>
  104. <template slot="header">
  105. <span class="sort-header" @click.stop="onSortClick('carbs',$event)" @dblclick.stop="onSortToggle('carbs')">碳水<span v-if="sortIndex('carbs') !== null" class="sort-index" :class="sortClass('carbs') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('carbs') }}</span>{{ sortIcon('carbs') }}</span>
  106. </template>
  107. </el-table-column>
  108. <el-table-column label="膳食纤维" width="90" align="right" :class-name="sortClass('fiber')">
  109. <template slot-scope="{ row }">
  110. <span class="num-val">{{ row.fiber != null ? row.fiber : '-' }}</span>
  111. <span class="num-unit">g</span>
  112. </template>
  113. <template slot="header">
  114. <span class="sort-header" @click.stop="onSortClick('fiber',$event)" @dblclick.stop="onSortToggle('fiber')">纤维<span v-if="sortIndex('fiber') !== null" class="sort-index" :class="sortClass('fiber') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('fiber') }}</span>{{ sortIcon('fiber') }}</span>
  115. </template>
  116. </el-table-column>
  117. <el-table-column label="能量(kJ)" width="90" align="right" :class-name="sortClass('energyKj')">
  118. <template slot-scope="{ row }">
  119. <span class="num-val">{{ row.energyKj != null ? row.energyKj : '-' }}</span>
  120. </template>
  121. <template slot="header">
  122. <span class="sort-header" @click.stop="onSortClick('energyKj',$event)" @dblclick.stop="onSortToggle('energyKj')">能量<span v-if="sortIndex('energyKj') !== null" class="sort-index" :class="sortClass('energyKj') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('energyKj') }}</span>{{ sortIcon('energyKj') }}</span>
  123. </template>
  124. </el-table-column>
  125. <el-table-column label="胆固醇" width="90" align="right" :class-name="sortClass('cholesterol')">
  126. <template slot-scope="{ row }">
  127. <span v-if="row.cholesterol != null" class="num-val">{{ row.cholesterol }}<span class="num-unit">mg</span></span>
  128. <span v-else>-</span>
  129. </template>
  130. <template slot="header">
  131. <span class="sort-header" @click.stop="onSortClick('cholesterol',$event)" @dblclick.stop="onSortToggle('cholesterol')">胆固醇<span v-if="sortIndex('cholesterol') !== null" class="sort-index" :class="sortClass('cholesterol') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('cholesterol') }}</span>{{ sortIcon('cholesterol') }}</span>
  132. </template>
  133. </el-table-column>
  134. <el-table-column prop="status" label="状态" width="80" align="center" :class-name="sortClass('status')">
  135. <template slot-scope="{ row }">
  136. <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="mini" effect="dark">
  137. {{ row.status === 'active' ? '启用' : '停用' }}
  138. </el-tag>
  139. </template>
  140. <template slot="header">
  141. <span class="sort-header" @click.stop="onSortClick('status',$event)" @dblclick.stop="onSortToggle('status')">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
  142. </template>
  143. </el-table-column>
  144. <el-table-column label="操作" width="180" fixed="right" align="center">
  145. <template slot-scope="{ row }">
  146. <el-button size="mini" type="primary" plain @click="editItem(row)">编辑</el-button>
  147. <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
  148. <el-button size="mini" plain>
  149. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  150. </el-button>
  151. <el-dropdown-menu slot="dropdown">
  152. <el-dropdown-item command="detail" icon="el-icon-view">查看详情</el-dropdown-item>
  153. <el-dropdown-item command="delete" icon="el-icon-delete" divided style="color: #f56c6c;">删除</el-dropdown-item>
  154. </el-dropdown-menu>
  155. </el-dropdown>
  156. </template>
  157. </el-table-column>
  158. </el-table>
  159. <div class="pagination-wrap">
  160. <el-pagination
  161. @size-change="handleSizeChange"
  162. @current-change="handleCurrentChange"
  163. :current-page="page"
  164. :page-sizes="[10, 20, 50, 100]"
  165. :page-size="size"
  166. layout="total, sizes, prev, pager, next, jumper"
  167. :total="total"
  168. />
  169. </div>
  170. </el-card>
  171. <!-- 编辑/新建对话框 -->
  172. <el-dialog
  173. :title="editId ? '编辑食材' : '新建食材'"
  174. :visible.sync="dialogVisible"
  175. width="680px"
  176. :close-on-click-modal="false"
  177. >
  178. <el-form label-width="130px" size="small">
  179. <el-row :gutter="20">
  180. <el-col :span="12">
  181. <el-form-item label="名称">
  182. <el-input v-model="form.name" placeholder="食材名称" />
  183. </el-form-item>
  184. </el-col>
  185. <el-col :span="12">
  186. <el-form-item label="分类">
  187. <el-select v-model="form.category" style="width:100%" placeholder="选择分类">
  188. <el-option v-for="opt in categoryOptions" :key="opt" :label="opt" :value="opt" />
  189. </el-select>
  190. </el-form-item>
  191. </el-col>
  192. </el-row>
  193. <el-row :gutter="20">
  194. <el-col :span="12">
  195. <el-form-item label="热量(kcal/100g)">
  196. <el-input-number v-model="form.calories" :min="0" :precision="1" style="width:100%" />
  197. </el-form-item>
  198. </el-col>
  199. <el-col :span="12">
  200. <el-form-item label="能量(kJ/100g)">
  201. <el-input-number v-model="form.energyKj" :min="0" :precision="1" style="width:100%" />
  202. </el-form-item>
  203. </el-col>
  204. </el-row>
  205. <el-row :gutter="20">
  206. <el-col :span="8">
  207. <el-form-item label="蛋白质(g)">
  208. <el-input-number v-model="form.protein" :min="0" :precision="1" style="width:100%" />
  209. </el-form-item>
  210. </el-col>
  211. <el-col :span="8">
  212. <el-form-item label="碳水(g)">
  213. <el-input-number v-model="form.carbs" :min="0" :precision="1" style="width:100%" />
  214. </el-form-item>
  215. </el-col>
  216. <el-col :span="8">
  217. <el-form-item label="脂肪(g)">
  218. <el-input-number v-model="form.fat" :min="0" :precision="1" style="width:100%" />
  219. </el-form-item>
  220. </el-col>
  221. </el-row>
  222. <el-row :gutter="20">
  223. <el-col :span="8">
  224. <el-form-item label="膳食纤维(g)">
  225. <el-input-number v-model="form.fiber" :min="0" :precision="1" style="width:100%" />
  226. </el-form-item>
  227. </el-col>
  228. <el-col :span="8">
  229. <el-form-item label="淀粉(g)">
  230. <el-input-number v-model="form.starch" :min="0" :precision="1" style="width:100%" />
  231. </el-form-item>
  232. </el-col>
  233. <el-col :span="8">
  234. <el-form-item label="胆固醇(mg)">
  235. <el-input-number v-model="form.cholesterol" :min="0" :precision="1" style="width:100%" />
  236. </el-form-item>
  237. </el-col>
  238. </el-row>
  239. <el-form-item label="单位">
  240. <el-input v-model="form.unit" placeholder="如: 克、个、份" style="width: 200px;" />
  241. </el-form-item>
  242. <el-form-item label="应季月份">
  243. <el-checkbox-group v-model="form.months">
  244. <el-checkbox v-for="m in 12" :key="m" :label="m" border size="mini">{{ m }}月</el-checkbox>
  245. </el-checkbox-group>
  246. </el-form-item>
  247. <el-form-item label="状态">
  248. <el-radio-group v-model="form.status">
  249. <el-radio label="active">启用</el-radio>
  250. <el-radio label="inactive">停用</el-radio>
  251. </el-radio-group>
  252. </el-form-item>
  253. </el-form>
  254. <div slot="footer">
  255. <el-button @click="dialogVisible = false">取消</el-button>
  256. <el-button type="primary" :loading="submitting" @click="saveItem">保存</el-button>
  257. </div>
  258. </el-dialog>
  259. <!-- 详情对话框 -->
  260. <el-dialog title="食材详情" :visible.sync="detailVisible" width="500px">
  261. <el-descriptions :column="2" border size="small" v-if="detailData">
  262. <el-descriptions-item label="名称" :span="2">{{ detailData.name }}</el-descriptions-item>
  263. <el-descriptions-item label="分类">{{ detailData.category }}</el-descriptions-item>
  264. <el-descriptions-item label="状态">
  265. <el-tag :type="detailData.status === 'active' ? 'success' : 'info'" size="mini">{{ detailData.status === 'active' ? '启用' : '停用' }}</el-tag>
  266. </el-descriptions-item>
  267. <el-descriptions-item label="热量(kcal)">{{ detailData.calories || '-' }}</el-descriptions-item>
  268. <el-descriptions-item label="能量(kJ)">{{ detailData.energyKj || '-' }}</el-descriptions-item>
  269. <el-descriptions-item label="蛋白质(g)">{{ detailData.protein || '-' }}</el-descriptions-item>
  270. <el-descriptions-item label="脂肪(g)">{{ detailData.fat || '-' }}</el-descriptions-item>
  271. <el-descriptions-item label="碳水(g)">{{ detailData.carbs || '-' }}</el-descriptions-item>
  272. <el-descriptions-item label="膳食纤维(g)">{{ detailData.fiber || '-' }}</el-descriptions-item>
  273. <el-descriptions-item label="淀粉(g)">{{ detailData.starch || '-' }}</el-descriptions-item>
  274. <el-descriptions-item label="胆固醇(mg)">{{ detailData.cholesterol || '-' }}</el-descriptions-item>
  275. <el-descriptions-item label="单位">{{ detailData.unit || '-' }}</el-descriptions-item>
  276. <el-descriptions-item label="排序">{{ detailData.sortOrder != null ? detailData.sortOrder : '-' }}</el-descriptions-item>
  277. <el-descriptions-item label="应季月份" :span="2">
  278. <span v-if="detailData.months && detailData.months.length">{{ detailData.months.map(function(m) { return m + '月' }).join('、') }}</span>
  279. <span v-else style="color:#999">未设置</span>
  280. </el-descriptions-item>
  281. </el-descriptions>
  282. </el-dialog>
  283. </div>
  284. </template>
  285. <script>
  286. import { getFoodList, createFood, updateFood, deleteFood, getFoodDetail, saveFoodMonths } from '@/api/admin'
  287. export default {
  288. name: 'Foods',
  289. data() {
  290. return {
  291. submitting: false,
  292. loading: false,
  293. list: [],
  294. total: 0,
  295. page: 1,
  296. size: 20,
  297. categoryFilter: '',
  298. keyword: '',
  299. editId: null,
  300. dialogVisible: false,
  301. detailVisible: false,
  302. detailData: null,
  303. categoryOptions: [
  304. '蔬菜', '水果', '水产品', '肉类', '干果',
  305. '主食', '豆类及豆制品', '蛋类', '乳制品', '快餐', '汤'
  306. ],
  307. sortState: [],
  308. sortColLabels: {
  309. name: '\u540d\u79f0', category: '\u5206\u7c7b', calories: '\u70ed\u91cf', protein: '\u86cb\u767d',
  310. carbs: '\u78b3\u6c34', fat: '\u8102\u80aa', fiber: '\u7ea4\u7ef4', energyKj: '\u80fd\u91cf',
  311. starch: '\u6dc0\u7c89', cholesterol: '\u80c6\u56fa\u9187', status: '\u72b6\u6001'
  312. },
  313. form: {
  314. name: '',
  315. category: '',
  316. calories: null,
  317. protein: null,
  318. carbs: null,
  319. fat: null,
  320. fiber: null,
  321. energyKj: null,
  322. starch: null,
  323. cholesterol: null,
  324. unit: '克',
  325. status: 'active',
  326. months: []
  327. }
  328. }
  329. },
  330. computed: {
  331. tableHeight() {
  332. return window.innerHeight - 340
  333. },
  334. sortLabel() {
  335. if (!this.sortState || this.sortState.length === 0) return ''
  336. return this.sortState.map(s => {
  337. const col = this.sortColLabels[s.column] || s.column
  338. return s.order === 'asc' ? col + '↑' : col + '↓'
  339. }).join(' > ')
  340. }
  341. },
  342. created() {
  343. this.loadList()
  344. },
  345. methods: {
  346. async loadList() {
  347. this.loading = true
  348. try {
  349. const sortParams = { page: this.page, size: this.size }
  350. if (this.categoryFilter) sortParams.category = this.categoryFilter
  351. if (this.keyword) sortParams.keyword = this.keyword
  352. if (this.sortState.length > 0) sortParams.sort = this.sortState
  353. const res = await getFoodList(sortParams)
  354. this.list = res.data && res.data.records || []
  355. this.total = res.data && res.data.total || 0
  356. } catch (e) {
  357. this.$message && this.$message.error && this.$message.error('加载失败')
  358. } finally {
  359. this.loading = false
  360. }
  361. },
  362. handleSearch() {
  363. this.sortState = []
  364. this.loadList()
  365. },
  366. onSortClick(col, event) {
  367. const idx = this.sortState.findIndex(s => s.column === col)
  368. if (idx >= 0) {
  369. if (this.sortState[idx].order === 'asc') {
  370. this.sortState[idx].order = 'desc'
  371. } else {
  372. this.sortState.splice(idx, 1)
  373. }
  374. } else {
  375. this.sortState.push({ column: col, order: 'asc' })
  376. }
  377. this.loadList()
  378. },
  379. handleSizeChange(val) {
  380. this.size = val
  381. this.page = 1
  382. this.loadList()
  383. },
  384. handleCurrentChange(val) {
  385. this.page = val
  386. this.loadList()
  387. },
  388. openCreate() {
  389. this.editId = null
  390. this.form = { name: '', category: '', calories: null, protein: null, carbs: null, fat: null, fiber: null, energyKj: null, starch: null, cholesterol: null, unit: '克', status: 'active', months: [] }
  391. this.dialogVisible = true
  392. },
  393. async editItem(row) {
  394. this.editId = row.id
  395. this.form = {
  396. name: row.name || '',
  397. category: row.category || '',
  398. calories: row.calories,
  399. protein: row.protein,
  400. carbs: row.carbs,
  401. fat: row.fat,
  402. fiber: row.fiber,
  403. energyKj: row.energyKj,
  404. starch: row.starch,
  405. cholesterol: row.cholesterol,
  406. unit: row.unit || '克',
  407. status: row.status || 'active',
  408. months: []
  409. }
  410. // Load months for editing
  411. try {
  412. const res = await getFoodDetail(row.id)
  413. if (res.data && res.data.data && res.data.data.months) {
  414. this.form.months = res.data.data.months
  415. }
  416. } catch (e) {
  417. // months stay empty
  418. }
  419. this.dialogVisible = true
  420. },
  421. async saveItem() {
  422. this.submitting = true
  423. try {
  424. const data = { ...this.form }
  425. const months = data.months
  426. delete data.months
  427. let foodId = this.editId
  428. if (foodId) {
  429. data.id = foodId
  430. await updateFood(data)
  431. this.$message.success('更新成功')
  432. } else {
  433. const res = await createFood(data)
  434. foodId = res.data && res.data.data && res.data.data.id
  435. this.$message.success('创建成功')
  436. }
  437. // Save seasonal months
  438. if (foodId && months && months.length > 0) {
  439. try {
  440. await saveFoodMonths(foodId, months)
  441. } catch (e) {
  442. this.$message.warning('应季月份保存失败')
  443. }
  444. }
  445. this.dialogVisible = false
  446. this.loadList()
  447. } catch (e) {
  448. this.$message.error('保存失败')
  449. } finally {
  450. this.submitting = false
  451. }
  452. },
  453. deleteItem(row) {
  454. this.$confirm('确定删除 "' + row.name + '" 吗?', '提示', { type: 'warning' }).then(async () => {
  455. try {
  456. await deleteFood(row.id)
  457. this.$message.success('删除成功')
  458. this.loadList()
  459. } catch (e) {
  460. this.$message.error('删除失败')
  461. }
  462. }).catch(() => {})
  463. },
  464. showDetail(row) {
  465. this.detailData = row
  466. this.detailVisible = true
  467. },
  468. handleActionCmd(row, cmd) {
  469. switch (cmd) {
  470. case 'delete':
  471. this.deleteItem(row)
  472. break
  473. case 'detail':
  474. this.showDetail(row)
  475. break
  476. }
  477. },
  478. categoryTagType(category) {
  479. const map = {
  480. '蔬菜': 'success',
  481. '水果': 'warning',
  482. '水产品': 'primary',
  483. '肉类': 'danger',
  484. '干果': 'warning',
  485. '主食': '',
  486. '豆类及豆制品': 'success',
  487. '蛋类': 'danger',
  488. '乳制品': 'primary',
  489. '快餐': 'info',
  490. '汤': ''
  491. }
  492. return map[category] || ''
  493. }
  494. }
  495. }
  496. </script>
  497. <style scoped>
  498. .header-actions {
  499. display: flex;
  500. align-items: center;
  501. flex-wrap: wrap;
  502. gap: 4px;
  503. }
  504. .pagination-wrap {
  505. display: flex;
  506. justify-content: flex-end;
  507. padding: 16px 0 0;
  508. }
  509. .num-val {
  510. font-weight: 500;
  511. font-family: 'Helvetica Neue', Arial, sans-serif;
  512. }
  513. .num-unit {
  514. font-size: 11px;
  515. color: #999;
  516. margin-left: 2px;
  517. }
  518. .el-checkbox.is-bordered {
  519. margin-right: 6px;
  520. margin-bottom: 4px;
  521. }
  522. .el-descriptions {
  523. margin-top: 8px;
  524. }
  525. .sort-header {
  526. cursor: pointer;
  527. user-select: none;
  528. font-weight: 500;
  529. font-size: 12px;
  530. }
  531. .sort-header:hover {
  532. color: #409eff;
  533. }
  534. .sort-asc ::v-deep .el-table__header-wrapper th .cell,
  535. .sort-desc ::v-deep .el-table__header-wrapper th .cell {
  536. color: #409eff;
  537. font-weight: 600;
  538. }
  539. .sort-tag {
  540. margin-left: 8px;
  541. cursor: pointer;
  542. }
  543. </style>