TemplateManagement.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <script lang="ts" setup>
  2. import { useCalculatingHeight } from '@/hooks/calculatingHeight'
  3. import TableView from './components/TableView'
  4. import { useRouter } from 'vue-router'
  5. const router = useRouter()
  6. const filterRef: Ref<HTMLElement | null> = ref(null)
  7. const containerHeight = useCalculatingHeight(document.documentElement, 250, [filterRef])
  8. const queryData = ref({
  9. text_search: '',
  10. is_active: '',
  11. application_scope: '',
  12. party_id: ''
  13. })
  14. const aiModelList = ref([])
  15. const activeOptions = [
  16. {
  17. label: 'Active',
  18. value: true
  19. },
  20. {
  21. label: 'Inactive',
  22. value: false
  23. }
  24. ]
  25. const applicationScopeOptions = [
  26. {
  27. label: 'All Users',
  28. value: 'all'
  29. },
  30. {
  31. label: 'Specific Users',
  32. value: 'specific'
  33. }
  34. ]
  35. const tableRef = ref()
  36. onMounted(() => {
  37. $api.getFilterPartyID().then((res) => {
  38. if (res.code === 200) {
  39. aiModelList.value = res.data
  40. }
  41. })
  42. })
  43. const Search = () => {
  44. tableRef.value.searchTableData(queryData.value)
  45. }
  46. const handleCreate = () => {
  47. // Navigate to the Create Report Template page
  48. router.push({
  49. name: 'Create Report Template'
  50. })
  51. }
  52. import { debounce } from 'lodash'
  53. import axios from 'axios'
  54. const emit = defineEmits(['changeData'])
  55. const changeData = (val: string[]) => {
  56. // 同步选中状态
  57. emit('changeData', val)
  58. }
  59. interface ListItem {
  60. label: string
  61. id: string
  62. code: string
  63. }
  64. const options = ref<ListItem[]>([])
  65. const loading = ref(false)
  66. const currentController = ref<AbortController | null>(null)
  67. const handleVisibleChange = (visible) => {
  68. !visible && (options.value = [])
  69. }
  70. const remoteMethod = (query: string) => {
  71. currentController.value?.abort()
  72. const newController = new AbortController()
  73. currentController.value = newController
  74. loading.value = true
  75. $api
  76. .getSpecificRolesPartyID({ term: query }, { signal: newController.signal })
  77. .then((res) => {
  78. if (!newController.signal.aborted && res.code === 200) {
  79. options.value = (res.data || []).map((item) => ({
  80. id: item.id,
  81. code: item.code,
  82. label: item.label
  83. }))
  84. }
  85. })
  86. .catch((err) => {
  87. options.value = []
  88. if (!axios.isCancel(err) && !newController.signal.aborted) {
  89. ElMessage.error('Failed to load options')
  90. }
  91. })
  92. .finally(() => {
  93. // 仅当这是最新请求时,才关闭 loading
  94. if (currentController.value === newController) {
  95. loading.value = false
  96. }
  97. })
  98. }
  99. // 防抖版本(可选)
  100. const debouncedRemoteMethod = debounce(remoteMethod, 200)
  101. onUnmounted(() => {
  102. currentController.value?.abort()
  103. })
  104. </script>
  105. <template>
  106. <div class="dashboard">
  107. <div class="Title">
  108. <span>Report Template Management</span>
  109. <el-button class="el-button--main" @click="handleCreate">
  110. <span class="font_family icon-icon_add_b"></span> Create New Report Template</el-button
  111. >
  112. </div>
  113. <div class="display">
  114. <div class="heaer_top">
  115. <div class="input-tips_filter">
  116. <el-input
  117. placeholder="Search report name"
  118. v-model="queryData.text_search"
  119. class="log_input"
  120. >
  121. <template #prefix>
  122. <span class="iconfont_icon">
  123. <svg class="iconfont icon_dark" aria-hidden="true">
  124. <use xlink:href="#icon-icon_search_b"></use>
  125. </svg>
  126. </span>
  127. </template>
  128. </el-input>
  129. </div>
  130. <div class="tips_filter">
  131. <el-select v-model="queryData.is_active" clearable placeholder="Is Active">
  132. <el-option
  133. v-for="item in activeOptions"
  134. :key="item.label"
  135. :label="item.label"
  136. :value="item.value"
  137. />
  138. </el-select>
  139. </div>
  140. <div class="tips_filter">
  141. <el-select
  142. v-model="queryData.application_scope"
  143. clearable
  144. placeholder="Application Scope"
  145. >
  146. <el-option
  147. v-for="item in applicationScopeOptions"
  148. :key="item.label"
  149. :label="item.label"
  150. :value="item.value"
  151. />
  152. </el-select>
  153. </div>
  154. <div class="party-id-tips-filter">
  155. <el-select
  156. v-model="queryData.party_id"
  157. multiple
  158. filterable
  159. reserve-keyword
  160. placeholder="Party IDs"
  161. :loading="loading"
  162. collapse-tags
  163. collapse-tags-tooltip
  164. :max-collapse-tags="1"
  165. popper-class="part-id-select-popper"
  166. :filter-method="debouncedRemoteMethod"
  167. @change="changeData"
  168. @visible-change="handleVisibleChange"
  169. >
  170. <el-option
  171. v-for="item in options"
  172. :key="item.code"
  173. :label="item.code"
  174. :value="item.code"
  175. >
  176. <div class="select-option">
  177. <el-checkbox :model-value="queryData.party_id.includes(item.code)">
  178. <span class="text-ellipsis" style="width: 240px">{{ item.code }}</span>
  179. </el-checkbox>
  180. </div>
  181. </el-option>
  182. </el-select>
  183. </div>
  184. <el-button class="el-button--dark" @click="Search">Search</el-button>
  185. </div>
  186. </div>
  187. <TableView :height="containerHeight" ref="tableRef"></TableView>
  188. </div>
  189. </template>
  190. <style lang="scss" scoped>
  191. .Title {
  192. display: flex;
  193. justify-content: space-between;
  194. height: 68px;
  195. border-bottom: 1px solid var(--color-border);
  196. font-size: var(--font-size-6);
  197. font-weight: 700;
  198. padding: 0 24px;
  199. align-items: center;
  200. }
  201. .heaer_top {
  202. margin-top: 6.57px;
  203. margin-bottom: 8px;
  204. padding-right: 8px;
  205. display: flex;
  206. }
  207. .display {
  208. border: 1px solid var(--color-border);
  209. border-width: 0 0 1px 0;
  210. padding-left: 23.52px;
  211. }
  212. .tips_filter {
  213. flex: 1;
  214. height: 30px;
  215. max-width: 190px;
  216. margin-right: 8px;
  217. }
  218. .party-id-tips-filter {
  219. flex: 1;
  220. height: 30px;
  221. width: 280px;
  222. max-width: 280px;
  223. margin-right: 8px;
  224. :deep(.el-tag) {
  225. max-width: 220px !important;
  226. }
  227. }
  228. .input-tips_filter {
  229. flex: 1;
  230. max-width: 320px;
  231. height: 32px;
  232. margin-right: 8px;
  233. :deep(.el-input__wrapper) {
  234. height: 32px;
  235. }
  236. }
  237. .date-tips_filter {
  238. flex: 1;
  239. max-width: 250px;
  240. height: 32px;
  241. margin-right: 8px;
  242. }
  243. .comparator-tips_filter {
  244. flex: 1;
  245. display: flex;
  246. align-items: center;
  247. max-width: 260px;
  248. height: 32px;
  249. margin-right: 8px;
  250. }
  251. .dashboard {
  252. position: relative;
  253. background-color: var(--color-mode);
  254. }
  255. .text-ellipsis {
  256. display: inline-block;
  257. text-overflow: ellipsis;
  258. white-space: nowrap;
  259. overflow: hidden;
  260. }
  261. </style>