call.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryFormRef" :inline="true" :label-width="labelWidth">
  4. <el-form-item :label="t('common.userId')" prop="username">
  5. <el-input
  6. v-model="queryParams.userId"
  7. :placeholder="t('common.pleaseEnter') + t('common.userId')"
  8. clearable
  9. @keyup.enter="handleQuery"
  10. />
  11. </el-form-item>
  12. <el-form-item :label="t('common.userName')" prop="username">
  13. <el-input
  14. v-model="queryParams.username"
  15. :placeholder="t('common.pleaseEnter') + t('common.userName')"
  16. clearable
  17. @keyup.enter="handleQuery"
  18. />
  19. </el-form-item>
  20. <el-form-item>
  21. <el-button type="primary" icon="Search" @click="handleQuery">{{ t('common.search') }}</el-button>
  22. <el-button icon="Refresh" @click="resetQuery">{{ t('common.reset') }}</el-button>
  23. </el-form-item>
  24. </el-form>
  25. <el-row :gutter="10" class="mb8">
  26. <el-col :span="1.5">
  27. <el-button
  28. type="info"
  29. plain
  30. icon="Sort"
  31. @click="toggleExpandAll"
  32. >{{ t('common.expandCollapse') }}</el-button>
  33. </el-col>
  34. </el-row>
  35. <el-table
  36. v-if="refreshTable"
  37. v-loading="loading"
  38. :data="treeData"
  39. :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  40. row-key="id"
  41. :default-expand-all="isExpandAll"
  42. >
  43. <el-table-column type="selection" width="55" align="center" />
  44. <el-table-column :label="t('common.userName')" prop="userName" width="200" show-overflow-tooltip>
  45. <template #default="{ row }">
  46. <span v-if="row.isParent" class="parent-user-name">{{ row.userName || row.userId || t('common.unknown') }}</span>
  47. <span v-else>{{ row.userName || '-' }}</span>
  48. </template>
  49. </el-table-column>
  50. <el-table-column :label="t('common.userId')" prop="userId" width="120" align="center"/>
  51. <el-table-column :label="t('call.phoneNumber')" prop="phoneNumber" align="center" />
  52. <el-table-column :label="t('call.callType')" prop="callType" align="center">
  53. <template #default="{ row }">
  54. <span v-if="!row.isParent">{{ callTypeText(row.callType) }}</span>
  55. </template>
  56. </el-table-column>
  57. <el-table-column :label="t('call.startTime')" prop="startTime" align="center" />
  58. <el-table-column :label="t('call.duration')" prop="duration" align="center" />
  59. <el-table-column :label="t('call.contactName')" prop="contactName" align="center" />
  60. <el-table-column :label="t('common.operation')" align="center" width="140" class-name="small-padding fixed-width">
  61. <template #default="{ row }">
  62. <el-tooltip :content="t('common.view')" placement="top" v-if="!row.isParent">
  63. <el-button link type="primary" icon="View" @click.stop="handleView(row)"></el-button>
  64. </el-tooltip>
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  69. <el-dialog :title="title" v-model="open" width="600px" append-to-body>
  70. <el-form ref="formRef" :model="form" :label-width="labelWidth">
  71. <el-form-item :label="t('common.userId')" prop="userId">
  72. <el-input v-model="form.userId" :placeholder="t('common.pleaseEnter') + t('common.userId')" />
  73. </el-form-item>
  74. <el-form-item :label="t('common.userName')" prop="userName">
  75. <el-input v-model="form.userName" />
  76. </el-form-item>
  77. <el-form-item :label="t('call.phoneNumber')" prop="phoneNumber">
  78. <el-input v-model="form.phoneNumber" :placeholder="t('call.pleaseEnterPhoneNumber')" />
  79. </el-form-item>
  80. <el-form-item :label="t('call.startTime')" prop="startTime">
  81. <el-input v-model="form.startTime" :placeholder="t('call.pleaseEnterStartTime')" />
  82. </el-form-item>
  83. <el-form-item :label="t('call.duration')" prop="duration">
  84. <el-input v-model="form.duration" :placeholder="t('call.pleaseEnterDuration')" />
  85. </el-form-item>
  86. <el-form-item :label="t('call.contactName')" prop="contactName">
  87. <el-input v-model="form.contactName" :placeholder="t('call.pleaseEnterContactName')" />
  88. </el-form-item>
  89. </el-form>
  90. <template #footer>
  91. <div class="dialog-footer">
  92. <el-button @click="cancel">{{ t('common.cancel') }}</el-button>
  93. </div>
  94. </template>
  95. </el-dialog>
  96. </div>
  97. </template>
  98. <script setup>
  99. import {ref, reactive, getCurrentInstance, onMounted, nextTick, computed} from 'vue'
  100. import {getCallPageList} from '@/api/system/call.js'
  101. import {useI18n} from '@/composables/useI18n'
  102. const { t, locale } = useI18n()
  103. // 根据语言动态设置label宽度
  104. const labelWidth = computed(() => {
  105. return locale.value === 'vi' ? '140px' : '80px'
  106. })
  107. const { proxy } = getCurrentInstance()
  108. const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable")
  109. const callList = ref([])
  110. const treeData = ref([])
  111. const total = ref(0)
  112. const title = ref("")
  113. const open = ref(false)
  114. const form = ref({})
  115. const formRef = ref()
  116. const loading = ref(true)
  117. const queryFormRef = ref()
  118. const isExpandAll = ref(false)
  119. const refreshTable = ref(true)
  120. const queryParams = reactive({
  121. pageNum: 1,
  122. pageSize: 10,
  123. username: null,
  124. userId:null,
  125. })
  126. // 将扁平数据转换为树形结构
  127. const convertToTree = (data) => {
  128. if (!data || !Array.isArray(data)) {
  129. return []
  130. }
  131. const treeMap = new Map()
  132. const result = []
  133. // 先按userName和userId分组
  134. data.forEach(item => {
  135. const key = `${item.userName || ''}_${item.userId || ''}`
  136. if (!treeMap.has(key)) {
  137. treeMap.set(key, {
  138. id: `parent_${key}`,
  139. userName: item.userName || item.userId || t('common.unknown'),
  140. userId: item.userId,
  141. isParent: true,
  142. children: []
  143. })
  144. }
  145. // 添加子节点
  146. treeMap.get(key).children.push({
  147. ...item,
  148. id: item.id || `child_${key}_${treeMap.get(key).children.length}`
  149. })
  150. })
  151. // 转换为数组
  152. treeMap.forEach(value => {
  153. result.push(value)
  154. })
  155. return result
  156. }
  157. onMounted(()=>{
  158. getList()
  159. })
  160. const callTypeText = (type) => {
  161. switch (type) {
  162. case 1:
  163. case '1':
  164. return t('call.incoming');
  165. case 2:
  166. case '2':
  167. return t('call.outgoing');
  168. case 3:
  169. case '3':
  170. return t('call.missed');
  171. case 5:
  172. case '5':
  173. return t('call.hangup');
  174. default:
  175. return type;
  176. }
  177. }
  178. // 表单重设
  179. const reset = () => {
  180. form.value = {
  181. id: null,
  182. phoneNumber: null,
  183. callType: null,
  184. startTime: null,
  185. duration: null,
  186. contactName: null,
  187. createTime: null,
  188. userId: null,
  189. userName:null,
  190. }
  191. if (formRef.value) {
  192. formRef.value.resetFields()
  193. }
  194. }
  195. /** 搜索按钮操作 */
  196. const handleQuery = () => {
  197. queryParams.pageNum = 1
  198. getList()
  199. }
  200. /** 重置按钮操作 */
  201. const resetQuery = () => {
  202. queryFormRef.value.resetFields()
  203. handleQuery()
  204. }
  205. const getList = () => {
  206. loading.value = true
  207. const params = {
  208. pageNum: queryParams.pageNum,
  209. pageSize: queryParams.pageSize,
  210. userName: queryParams.username,
  211. userId:queryParams.userId,
  212. }
  213. getCallPageList(params).then(response => {
  214. // 处理不同的API返回格式
  215. // response 可能是 { code: 200, msg: "操作成功", data: [...] } 或直接是数组
  216. let data = []
  217. let totalCount = 0
  218. if (Array.isArray(response)) {
  219. // 如果response直接是数组
  220. data = response
  221. totalCount = data.length
  222. } else if (Array.isArray(response.data)) {
  223. // 如果data是数组
  224. data = response.data
  225. totalCount = data.length
  226. } else if (response.data && Array.isArray(response.data.records)) {
  227. // 如果是分页格式 { records: [], total: ... }
  228. data = response.data.records
  229. totalCount = response.data.total || data.length
  230. }
  231. // 如果数据已经是树形结构(包含isParent和children),直接使用
  232. if (data.length > 0 && (data[0].isParent !== undefined || data[0].children !== undefined)) {
  233. treeData.value = data
  234. callList.value = data.flatMap(item => item.children || [])
  235. total.value = totalCount
  236. } else {
  237. // 否则转换为树形结构
  238. callList.value = data
  239. treeData.value = convertToTree(data)
  240. total.value = totalCount
  241. }
  242. loading.value = false
  243. }).catch((error) => {
  244. console.error('获取通话列表失败:', error)
  245. loading.value = false
  246. })
  247. }
  248. /** 查看按钮操作 */
  249. const handleView = (row) => {
  250. reset()
  251. form.value = {...row}
  252. open.value = true
  253. title.value = t('call.viewTitle')
  254. nextTick(() => {
  255. if (formRef.value) formRef.value.clearValidate && formRef.value.clearValidate()
  256. })
  257. }
  258. // 取消按钮
  259. const cancel = () => {
  260. open.value = false
  261. reset()
  262. }
  263. /** 展开/折叠操作 */
  264. const toggleExpandAll = () => {
  265. refreshTable.value = false
  266. isExpandAll.value = !isExpandAll.value
  267. nextTick(() => {
  268. refreshTable.value = true
  269. })
  270. }
  271. </script>
  272. <style scoped lang="scss">
  273. .parent-user-name {
  274. color: #409EFF;
  275. font-weight: 500;
  276. font-size: 14px;
  277. }
  278. </style>