You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
7.4 KiB

11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
11 months ago
10 months ago
11 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
  1. <template>
  2. <el-dialog
  3. title="数据源选择"
  4. :visible="visible"
  5. :close-on-click-modal="false"
  6. width="880px"
  7. top="5vh"
  8. append-to-body
  9. custom-class="data-source-dialog"
  10. @close="handleClose"
  11. >
  12. <!-- 主体我们放一个专门的可滚动区域 -->
  13. <div class="data-source-body">
  14. <el-form label-position="top" class="data-source-form">
  15. <!-- 可选分组显示 -->
  16. <template v-if="hasViewGroups">
  17. <div
  18. v-for="group in viewGroups"
  19. :key="group.viewSource"
  20. class="view-group"
  21. >
  22. <div class="view-title">
  23. {{ group.viewSource + ' (' + group.fields.length + '个字段)' }}
  24. </div>
  25. <div class="checkbox-grid">
  26. <label
  27. v-for="key in group.fields"
  28. :key="key.fieldName"
  29. class="checkbox-item">
  30. <el-checkbox :label="key.fieldName" v-model="selectedKeys">
  31. <span class="label-inner">
  32. <span class="field-name" :title="getFieldDisplayName(key)">{{ getFieldDisplayName(key) }}</span>
  33. </span>
  34. </el-checkbox>
  35. </label>
  36. </div>
  37. </div>
  38. </template>
  39. <template v-else>
  40. <div class="view-group">
  41. <div class="view-title">可用数据字段</div>
  42. <div class="checkbox-grid">
  43. <label
  44. v-for="key in dataKeys"
  45. :key="key.fieldName"
  46. class="checkbox-item">
  47. <el-checkbox :label="key.fieldName" v-model="selectedKeys">
  48. <span class="label-inner">
  49. <span class="field-name" :title="getFieldDisplayName(key)">{{ getFieldDisplayName(key) }}</span>
  50. </span>
  51. </el-checkbox>
  52. </label>
  53. </div>
  54. </div>
  55. </template>
  56. </el-form>
  57. </div>
  58. <!-- footer 使用 el-dialog footer slot保持默认位置 -->
  59. <div slot="footer" class="dialog-footer">
  60. <el-button @click="handleClose">取消</el-button>
  61. <el-button type="primary" @click="handleConfirm">确定</el-button>
  62. </div>
  63. </el-dialog>
  64. </template>
  65. <script>
  66. export default {
  67. name: 'DataSourceDialog',
  68. props: {
  69. visible: Boolean,
  70. dataKeys: {
  71. type: Array,
  72. default: () => []
  73. },
  74. currentText: {
  75. type: String,
  76. default: ''
  77. },
  78. sourceType: {
  79. type: String,
  80. default: 'text'
  81. }
  82. },
  83. emits: ['update:visible', 'confirm'],
  84. data() {
  85. return {
  86. selectedKeys: []
  87. }
  88. },
  89. computed: {
  90. hasViewGroups() {
  91. return this.dataKeys && this.dataKeys.some(k => k.viewSource)
  92. },
  93. viewGroups() {
  94. if (!this.hasViewGroups) return []
  95. const groups = {}
  96. this.dataKeys.forEach(key => {
  97. const vs = key.viewSource || '未知视图'
  98. if (!groups[vs]) groups[vs] = {viewSource: vs, fields: []}
  99. groups[vs].fields.push(key)
  100. })
  101. return Object.values(groups)
  102. }
  103. },
  104. watch: {
  105. visible(newVal) {
  106. if (newVal) this.initializeSelection()
  107. },
  108. dataKeys() {
  109. // 当字段更新时,重新初始化选中(如果对话框是可见的)
  110. if (this.visible) this.initializeSelection()
  111. }
  112. },
  113. methods: {
  114. getFieldDisplayName(key) {
  115. const name = key.displayName || key.fieldName
  116. return name + (key.fieldDescription ? `(${key.fieldDescription})` : '')
  117. },
  118. initializeSelection() {
  119. if (!this.currentText || !this.dataKeys || !this.dataKeys.length) {
  120. this.selectedKeys = []
  121. return
  122. }
  123. const found = []
  124. this.dataKeys.forEach(k => {
  125. const pat = new RegExp(`#\\{${k.fieldName}\\}`, 'g')
  126. if (pat.test(this.currentText)) found.push(k.fieldName)
  127. })
  128. this.selectedKeys = found
  129. },
  130. handleClose() {
  131. this.$emit('update:visible', false)
  132. },
  133. handleConfirm() {
  134. const processed = this.selectedKeys.map(s => {
  135. const f = this.dataKeys.find(k => k.fieldName === s)
  136. return f && f.originalFieldName ? f.originalFieldName : s
  137. })
  138. this.$emit('confirm', processed, this.sourceType)
  139. this.handleClose()
  140. }
  141. }
  142. }
  143. </script>
  144. <style scoped>
  145. /* ---------- 对话框整体 ---------- */
  146. .data-source-dialog {
  147. /* 确保 dialog 在 body 上并覆盖画布 */
  148. }
  149. .el-dialog__wrapper {
  150. z-index: 3000 !important; /* 提升到高于页面画布的层级 */
  151. }
  152. /* 把 el-dialog 设置成 flex 布局:header/body/footer 三段式 */
  153. .data-source-dialog .el-dialog {
  154. display: flex;
  155. flex-direction: column;
  156. max-height: 90vh;
  157. box-sizing: border-box;
  158. }
  159. /* 中间真正滚动的区域:固定高度(max)并可滚动,避免内容跑出弹窗 */
  160. .data-source-body {
  161. flex: 1 1 auto;
  162. overflow: auto;
  163. padding: 12px 18px;
  164. box-sizing: border-box;
  165. }
  166. /* 表单内容器布局 */
  167. .data-source-form {
  168. margin: 0;
  169. }
  170. /* 分组标题 */
  171. .view-group {
  172. margin-bottom: 12px;
  173. padding: 10px;
  174. background: #fafbfc;
  175. border: 1px solid #e8eef0;
  176. border-radius: 8px;
  177. box-sizing: border-box;
  178. }
  179. .view-title {
  180. color: #17b3a3;
  181. font-weight: 700;
  182. margin-bottom: 8px;
  183. font-size: 14px;
  184. }
  185. /* ---------- 复选框网格更紧凑 ---------- */
  186. .checkbox-grid {
  187. display: flex;
  188. flex-wrap: wrap;
  189. margin: -4px; /* 缩小外边距 */
  190. box-sizing: border-box;
  191. }
  192. .checkbox-item {
  193. padding: 4px;
  194. margin: 4px;
  195. width: calc(20% - 8px); /* 默认 5 列布局,更紧凑 */
  196. min-width: 150px; /* 允许更窄 */
  197. background: #fff;
  198. border: 1px solid #e6eef0;
  199. border-radius: 4px; /* 缩小圆角 */
  200. display: flex;
  201. align-items: flex-start;
  202. cursor: pointer;
  203. transition: background 0.12s;
  204. }
  205. .checkbox-item:hover {
  206. background: #f7fdfb;
  207. border-color: #cfeee8;
  208. }
  209. /* 内部的文字和 badge */
  210. .label-inner {
  211. display: flex;
  212. align-items: center;
  213. justify-content: space-between;
  214. gap: 4px; /* 缩小间距 */
  215. width: 100%;
  216. }
  217. .field-name {
  218. display: block;
  219. font-size: 12px;
  220. color: #333;
  221. line-height: 1.4;
  222. white-space: nowrap; /* 单行显示 */
  223. overflow: hidden; /* 隐藏溢出 */
  224. text-overflow: ellipsis; /* 超出显示 ... */
  225. max-width: 120px; /* 限制最大宽度 */
  226. }
  227. .duplicate-badge {
  228. padding: 1px 4px;
  229. font-size: 10px; /* 更小的 badge */
  230. border-radius: 3px;
  231. }
  232. /* 缩小复选框 */
  233. .checkbox-item .el-checkbox__inner {
  234. width: 14px !important;
  235. height: 14px !important;
  236. }
  237. /* 响应式:小屏幕保持紧凑 */
  238. @media (max-width: 1100px) {
  239. .checkbox-item { width: calc(25% - 8px); min-width: 140px; } /* 4 列 */
  240. }
  241. @media (max-width: 840px) {
  242. .checkbox-item { width: calc(33.333% - 8px); min-width: 130px; } /* 3 列 */
  243. }
  244. @media (max-width: 520px) {
  245. .checkbox-item { width: calc(50% - 8px); min-width: auto; } /* 2 列 */
  246. }
  247. /* ---------- Footer 固定在对话框底部(但不覆写内容) ---------- */
  248. .dialog-footer {
  249. padding: 10px 18px;
  250. border-top: 1px solid #e8eef0;
  251. display: flex;
  252. justify-content: center;
  253. gap: 14px;
  254. background: #fff;
  255. box-sizing: border-box;
  256. z-index: 40;
  257. }
  258. /* 按钮样式保持 */
  259. .dialog-footer .el-button {
  260. min-width: 88px;
  261. height: 36px;
  262. font-weight: 600;
  263. border-radius: 6px;
  264. }
  265. .dialog-footer .el-button--primary {
  266. background: linear-gradient(135deg, #17b3a3 0%, #1dc5ef 100%);
  267. color: #fff;
  268. border: none;
  269. }
  270. </style>