index.vue 12 KB

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