index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <template>
  2. <div class="app-container">
  3. <el-card>
  4. <!-- 第一行:科室班组个人按钮组 -->
  5. <div class="filter-container">
  6. <div class="button-group">
  7. <span class="button-group-label">查询范围:</span>
  8. <el-button v-for="item in queryTypeOptions" :key="item.value"
  9. :type="queryType === item.value ? 'primary' : 'default'"
  10. :class="queryType === item.value ? 'active' : 'inactive'" @click="handleQueryTypeChange(item.value)">
  11. {{ item.label }}
  12. </el-button>
  13. </div>
  14. <div class="button-group" style="margin-left: 20px;">
  15. <span class="button-group-label">时间范围:</span>
  16. <el-button v-for="item in timeRangeOptions" :key="item.value"
  17. :type="timeRange === item.value ? 'primary' : 'default'"
  18. :class="timeRange === item.value ? 'active' : 'inactive'" @click="handleTimeRangeChange(item.value)">
  19. {{ item.label }}
  20. </el-button>
  21. <!-- 自定义时间范围选择器 -->
  22. <el-date-picker v-if="timeRange === 'custom'" v-model="customDateRange" type="daterange" range-separator="至"
  23. start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD"
  24. style="margin-left: 10px; width: 240px;" @change="handleCustomDateChange" />
  25. </div>
  26. </div>
  27. <!-- 第二行:查询条件 -->
  28. <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="search-form">
  29. <!-- 大队下拉框 -->
  30. <el-form-item label="大队" prop="brigadeId" v-if="queryType === '3' || queryType === '2' || queryType === '1'">
  31. <el-select v-model="queryParams.brigadeId" placeholder="请选择大队" clearable style="width: 200px"
  32. @change="handleBrigadeChange">
  33. <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.text" :value="item.value" />
  34. </el-select>
  35. </el-form-item>
  36. <!-- 主管下拉框 -->
  37. <el-form-item label="主管" prop="deptId" v-if="queryType === '2' || queryType === '1'">
  38. <el-select v-model="queryParams.deptId" placeholder="请选择主管" clearable style="width: 200px"
  39. @change="handleQuery">
  40. <el-option v-for="item in departmentOptions" :key="item.value" :label="item.text" :value="item.value" />
  41. </el-select>
  42. </el-form-item>
  43. <!-- 班组下拉框 -->
  44. <el-form-item label="班组" prop="teamId" v-if="queryType === '1'">
  45. <el-select v-model="queryParams.teamId" placeholder="请选择班组" clearable style="width: 200px"
  46. @change="handleQuery">
  47. <el-option v-for="item in teamOptions" :key="item.value" :label="item.text" :value="item.value" />
  48. </el-select>
  49. </el-form-item>
  50. <!-- 姓名输入框 -->
  51. <el-form-item label="姓名" prop="userName" v-if="queryType === '1'">
  52. <el-input v-model="queryParams.userName" placeholder="请输入姓名" clearable style="width: 200px"
  53. @input="handleQuery" />
  54. </el-form-item>
  55. <!-- <el-form-item>
  56. <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
  57. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  58. </el-form-item> -->
  59. </el-form>
  60. <!-- 导出按钮 -->
  61. <div class="export-container">
  62. <el-button type="warning" icon="Download" @click="handleExport">导出</el-button>
  63. </div>
  64. <!-- 表格 -->
  65. <el-table v-loading="loading" :data="performanceList" border fit highlight-current-row
  66. style="width: 100%; margin-top: 20px;">
  67. <!-- 动态列 -->
  68. <el-table-column v-if="queryType === '4'" label="主管"
  69. prop="name" align="center" min-width="120" />
  70. <!-- 动态列 -->
  71. <el-table-column v-if="queryType === '3' || queryType === '2' || queryType === '1'" label="主管"
  72. :prop="queryType === '3' ? 'name' : 'deptName'" align="center" min-width="120" />
  73. <el-table-column v-if="queryType === '2' || queryType === '1'" label="班组"
  74. :prop="queryType === '2' ? 'name' : 'className'" align="center" min-width="120" />
  75. <el-table-column v-if="queryType === '1'" label="姓名" prop="name" align="center" min-width="100" />
  76. <!-- 固定列 -->
  77. <el-table-column label="总分" prop="totalScore" align="center" min-width="100" sortable
  78. :sort-orders="['ascending', 'descending']" @sort-change="handleSortChange('totalScore', $event)" />
  79. <el-table-column label="查获效率" prop="seizureEfficiency" align="center" min-width="120" sortable
  80. :sort-orders="['ascending', 'descending']" @sort-change="handleSortChange('seizureEfficiency', $event)" />
  81. <el-table-column label="抽问抽答正确率" prop="trainingScore" align="center" min-width="100" sortable
  82. :sort-orders="['ascending', 'descending']" @sort-change="handleSortChange('trainingScore', $event)" />
  83. <el-table-column label="巡检合格率" prop="inspectionPassRate" align="center" min-width="120" sortable
  84. :sort-orders="['ascending', 'descending']" @sort-change="handleSortChange('inspectionPassRate', $event)" />
  85. </el-table>
  86. <!-- 分页 -->
  87. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  88. v-model:limit="queryParams.pageSize" @pagination="getList" />
  89. </el-card>
  90. </div>
  91. </template>
  92. <script setup>
  93. import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue'
  94. import { getPerformanceList } from '@/api/performance/performance.js'
  95. import { listDept } from '@/api/system/dept.js'
  96. import { getDownload } from '@/utils/request.js'
  97. // 查询类型选项
  98. const queryTypeOptions = [
  99. { label: '大队', value: '4' },
  100. { label: '主管', value: '3' },
  101. { label: '班组', value: '2' },
  102. { label: '个人', value: '1' }
  103. ]
  104. // 时间范围选项
  105. const timeRangeOptions = [
  106. { label: '近三个月', value: 'threeMonths' },
  107. { label: '近六个月', value: 'sixMonths' },
  108. { label: '自定义', value: 'custom' }
  109. ]
  110. // 响应式数据
  111. const queryType = ref('4') // 默认查询科室
  112. const timeRange = ref('threeMonths') // 默认近三个月
  113. const customDateRange = ref([])
  114. const loading = ref(false)
  115. const total = ref(0)
  116. const queryFormRef = ref()
  117. // 查询参数
  118. const queryParams = reactive({
  119. pageNum: 1,
  120. pageSize: 10,
  121. brigadeId: '',
  122. deptId: '',
  123. teamId: '',
  124. userName: '',
  125. sortField: '',
  126. sortOrder: ''
  127. })
  128. // 绩效数据列表
  129. const performanceList = ref([])
  130. // 部门数据
  131. const deptList = ref([])
  132. const brigadeOptions = ref([])
  133. const departmentOptions = ref([])
  134. const teamOptions = ref([])
  135. // 大队变化处理函数
  136. const handleBrigadeChange = (brigadeId) => {
  137. // 清空主管和班组选项
  138. queryParams.deptId = null
  139. queryParams.teamId = null
  140. teamOptions.value = []
  141. if (brigadeId) {
  142. // 根据大队ID获取主管列表
  143. departmentOptions.value = deptList.value.filter(item =>
  144. item.deptType === 'MANAGER' && item.parentId === brigadeId
  145. ).map(item => ({
  146. value: item.deptId,
  147. text: item.deptName
  148. }))
  149. } else {
  150. // 如果没有选择大队,显示所有主管
  151. departmentOptions.value = deptList.value.filter(item => item.deptType === 'MANAGER').map(item => ({
  152. value: item.deptId,
  153. text: item.deptName
  154. }))
  155. }
  156. handleQuery()
  157. }
  158. // 监听科室变化,获取对应班组
  159. watch(() => queryParams.deptId, async (newDeptId) => {
  160. if (newDeptId) {
  161. await fetchTeamByDept(newDeptId)
  162. } else {
  163. // 如果清空科室选择,重置班组选项为所有班组
  164. teamOptions.value = deptList.value.filter(item => item.deptType === 'TEAMS').map(item => ({
  165. value: item.deptId,
  166. text: item.deptName
  167. }))
  168. queryParams.teamId = '' // 清空班组选择
  169. }
  170. })
  171. // 获取部门数据
  172. const fetchDeptData = async () => {
  173. try {
  174. const response = await listDept({})
  175. if (response.code === 200) {
  176. deptList.value = response.data || []
  177. // 生成大队选项
  178. brigadeOptions.value = deptList.value.filter(item => item.deptType === 'BRIGADE').map(item => ({
  179. value: item.deptId,
  180. text: item.deptName
  181. }))
  182. // 生成主管选项
  183. departmentOptions.value = deptList.value.filter(item => item.deptType === 'MANAGER').map(item => ({
  184. value: item.deptId,
  185. text: item.deptName
  186. }))
  187. // 生成班组选项
  188. teamOptions.value = deptList.value.filter(item => item.deptType === 'TEAMS').map(item => ({
  189. value: item.deptId,
  190. text: item.deptName
  191. }))
  192. }
  193. } catch (error) {
  194. console.error('获取部门数据失败:', error)
  195. }
  196. }
  197. // 根据科室ID获取班组列表
  198. const fetchTeamByDept = async (deptId) => {
  199. try {
  200. const response = await listDept({ parentId: deptId })
  201. if (response.code === 200 && response.data) {
  202. teamOptions.value = response.data.map(item => ({
  203. value: item.deptId,
  204. text: item.deptName
  205. }))
  206. // 清空班组选择,让用户重新选择
  207. queryParams.teamId = ''
  208. }
  209. } catch (error) {
  210. console.error('获取班组列表失败:', error)
  211. // 如果获取失败,重置为所有班组
  212. teamOptions.value = deptList.value.filter(item => item.deptType === 'TEAMS').map(item => ({
  213. value: item.deptId,
  214. text: item.deptName
  215. }))
  216. }
  217. }
  218. // 查询类型变化
  219. const handleQueryTypeChange = (type) => {
  220. queryType.value = type
  221. getList()
  222. }
  223. // 时间范围变化
  224. const handleTimeRangeChange = (range) => {
  225. timeRange.value = range
  226. if (range !== 'custom') {
  227. getList()
  228. }
  229. }
  230. // 格式化日期为 YYYY-MM-DD
  231. const formatDate = (date) => {
  232. const year = date.getFullYear()
  233. const month = String(date.getMonth() + 1).padStart(2, '0')
  234. const day = String(date.getDate()).padStart(2, '0')
  235. return `${year}-${month}-${day}`
  236. }
  237. // 计算时间范围
  238. const calculateTimeRange = (range) => {
  239. const now = new Date()
  240. const startTime = new Date()
  241. switch (range) {
  242. case 'threeMonths':
  243. startTime.setMonth(now.getMonth() - 3)
  244. break
  245. case 'sixMonths':
  246. startTime.setMonth(now.getMonth() - 6)
  247. break
  248. case 'custom':
  249. if (customDateRange.value && customDateRange.value.length === 2) {
  250. // 结束时间加一天
  251. const endDate = new Date(customDateRange.value[1])
  252. endDate.setDate(endDate.getDate() + 1)
  253. return {
  254. startTime: customDateRange.value[0],
  255. endTime: formatDate(endDate)
  256. }
  257. }
  258. return { startTime: '', endTime: '' }
  259. default:
  260. return { startTime: '', endTime: '' }
  261. }
  262. return {
  263. startTime: formatDate(startTime),
  264. endTime: formatDate(now)
  265. }
  266. }
  267. // 自定义时间范围变化
  268. const handleCustomDateChange = () => {
  269. if (customDateRange.value && customDateRange.value.length === 2) {
  270. getList()
  271. }
  272. }
  273. // 排序处理
  274. const handleSortChange = (field, { order }) => {
  275. queryParams.sortField = field
  276. queryParams.sortOrder = order === 'ascending' ? 'asc' : 'desc'
  277. getList()
  278. }
  279. // 查询
  280. const handleQuery = () => {
  281. queryParams.pageNum = 1
  282. getList()
  283. }
  284. // 重置查询
  285. const resetQuery = () => {
  286. queryFormRef.value.resetFields()
  287. queryParams.pageNum = 1
  288. queryParams.sortField = ''
  289. queryParams.sortOrder = ''
  290. getList()
  291. }
  292. // 获取绩效列表
  293. const getList = async () => {
  294. loading.value = true
  295. try {
  296. // 计算时间范围
  297. const timeRangeParams = calculateTimeRange(timeRange.value)
  298. // 构建请求参数
  299. const params = {
  300. ...queryParams,
  301. dimension: queryType.value,
  302. ...timeRangeParams
  303. }
  304. // 根据查询类型清理不必要的参数
  305. if (queryType.value === '3') {
  306. // 主管查询:只传递大队参数
  307. params.deptId = ''
  308. params.teamId = ''
  309. params.userName = ''
  310. } else if (queryType.value === '2') {
  311. // 班组查询:传递大队和主管参数
  312. params.teamId = ''
  313. params.userName = ''
  314. } else if (queryType.value === '1') {
  315. // 个人查询:传递所有参数
  316. } else if (queryType.value === '4') {
  317. // 大队查询:不传递任何部门参数
  318. params.brigadeId = ''
  319. params.deptId = ''
  320. params.teamId = ''
  321. params.userName = ''
  322. }
  323. // 调用API
  324. const response = await getPerformanceList(params)
  325. if (response.code === 200) {
  326. performanceList.value = response.data || []
  327. total.value = response.total || 0
  328. } else {
  329. console.error('获取绩效列表失败:', response.msg)
  330. performanceList.value = []
  331. total.value = 0
  332. }
  333. } catch (error) {
  334. console.error('获取绩效列表异常:', error)
  335. performanceList.value = []
  336. total.value = 0
  337. } finally {
  338. loading.value = false
  339. }
  340. }
  341. // 获取当前实例
  342. const { proxy } = getCurrentInstance()
  343. // 导出
  344. const handleExport = () => {
  345. try {
  346. // 计算时间范围
  347. const timeRangeParams = calculateTimeRange(timeRange.value)
  348. // 构建请求参数
  349. const params = {
  350. ...queryParams,
  351. dimension: queryType.value,
  352. ...timeRangeParams
  353. }
  354. // 根据查询类型清理不必要的参数
  355. if (queryType.value === '3') {
  356. // 主管查询:只传递大队参数
  357. params.deptId = ''
  358. params.teamId = ''
  359. params.userName = ''
  360. } else if (queryType.value === '2') {
  361. // 班组查询:传递大队和主管参数
  362. params.teamId = ''
  363. params.userName = ''
  364. } else if (queryType.value === '1') {
  365. // 个人查询:传递所有参数
  366. } else if (queryType.value === '4') {
  367. // 大队查询:不传递任何部门参数
  368. params.brigadeId = ''
  369. params.deptId = ''
  370. params.teamId = ''
  371. params.userName = ''
  372. }
  373. // 使用proxy.getDownload下载文件(GET请求)
  374. getDownload('/item/indicators/export', params, `绩效查询数据_${new Date().getTime()}.xlsx`)
  375. } catch (error) {
  376. console.error('导出异常:', error)
  377. proxy.$modal.msgError('导出异常,请重试')
  378. }
  379. }
  380. onMounted(() => {
  381. fetchDeptData()
  382. getList()
  383. })
  384. </script>
  385. <style lang="less" scoped>
  386. .app-container {
  387. padding: 20px;
  388. }
  389. .filter-container {
  390. display: flex;
  391. align-items: center;
  392. margin-bottom: 20px;
  393. }
  394. .button-group {
  395. display: flex;
  396. align-items: center;
  397. .button-group-label {
  398. margin-right: 10px;
  399. font-weight: 600;
  400. color: #606266;
  401. }
  402. .el-button {
  403. margin-right: 10px;
  404. &.active {
  405. background-color: #409EFF;
  406. border-color: #409EFF;
  407. color: #fff;
  408. }
  409. &.inactive {
  410. background-color: #fff;
  411. border-color: #409EFF;
  412. color: #409EFF;
  413. }
  414. }
  415. }
  416. .search-form {
  417. margin-bottom: 20px;
  418. }
  419. .export-container {
  420. margin-bottom: 20px;
  421. }
  422. :deep(.el-table) {
  423. .el-table__header-wrapper {
  424. th {
  425. background-color: #f5f7fa;
  426. font-weight: 600;
  427. }
  428. }
  429. }
  430. </style>