condition-group2.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <template>
  2. <div class="condition-group-item-wrap">
  3. <div>
  4. <el-form :model="formData" ref="formRef" label-suffix=":" label-position="right" label-width="100px" :disabled="disabled">
  5. <el-form-item label="数据周期" required prop="day">
  6. <el-select v-model="formData.day" @change="formData.exceptDay = 0">
  7. <el-option v-for="item in periodEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
  8. </el-select>
  9. </el-form-item>
  10. <el-form-item label="排除" required prop="exceptDay">
  11. <el-select v-model="formData.exceptDay">
  12. <el-option v-for="item in excludeEnum" :key="item.value" :label="item.label" :value="item.value" :disabled="formData.day <= item.value">
  13. </el-option>
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item
  17. v-for="(conditionInfo, index) of formData.items"
  18. :key="conditionInfo.dataType"
  19. :prop="getFormProp(conditionInfo, index)"
  20. :rules="{ validator: checkRight, trigger: getFormTrigger(conditionInfo) }">
  21. <template #label>
  22. <span v-if="index === 0">条件:</span>
  23. <span class="and-span" v-else>&</span>
  24. </template>
  25. <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap">
  26. <el-select v-model="conditionInfo.dataType" style="width: 140px" @change="changeDataType(conditionInfo)">
  27. <el-option
  28. v-for="item in candidateFields"
  29. :key="item.value"
  30. :disabled="selectedFields[item.value] ? true : false"
  31. :label="item.label"
  32. :value="item.value">
  33. </el-option>
  34. </el-select>
  35. <el-select
  36. v-model="conditionInfo.dayType"
  37. style="width: 80px"
  38. v-show="conditionInfo.symbol !== 'in' && conditionInfo.symbol !== 'not_in'">
  39. <el-option label="总计" value="sum"></el-option>
  40. <el-option label="均值" value="avg"></el-option>
  41. </el-select>
  42. <el-select v-model="conditionInfo.symbol" style="width: 120px">
  43. <el-option v-for="info in getSymbolOptions(conditionInfo.dataType)" :label="info.label" :value="info.value" :key="info.value">
  44. </el-option>
  45. </el-select>
  46. <template v-if="conditionInfo.symbol === 'in' || conditionInfo.symbol === 'not_in'">
  47. <el-select v-if="getFieldInfo(conditionInfo.dataType).options" v-model="conditionInfo.values" multiple collapse-tags>
  48. <el-option
  49. v-for="info in getFieldInfo(conditionInfo.dataType).options"
  50. :label="info.label"
  51. :value="info.value"
  52. :key="info.value"></el-option>
  53. </el-select>
  54. <div v-else style="width: 90%">
  55. <TagsInput :data="conditionInfo.values" placeholder="请输入至少一个关键词,多个关键词用换行符分隔" :disabled="disabled"></TagsInput>
  56. </div>
  57. </template>
  58. <template v-else-if="conditionInfo.symbol === 'between' || conditionInfo.symbol === 'not_between'">
  59. <RangeFloat :ranges="conditionInfo.ranges" :prefix="findPrefix(conditionInfo.dataType)" :suffix="findSuffix(conditionInfo.dataType)">
  60. </RangeFloat>
  61. </template>
  62. <InputFloat
  63. v-else
  64. v-model="conditionInfo.num"
  65. :prefix="findPrefix(conditionInfo.dataType)"
  66. :suffix="findSuffix(conditionInfo.dataType)"
  67. style="width: 200px">
  68. </InputFloat>
  69. <el-button @click="deleteCondition(index)" type="danger" :icon="Delete" v-show="formData.items.length > 1" link></el-button>
  70. </div>
  71. </el-form-item>
  72. </el-form>
  73. <el-button
  74. link
  75. type="primary"
  76. @click="addConditionItem"
  77. :icon="Plus"
  78. :style="{'margin-left': '100px', color: disabled ? '': 'blue'}"
  79. v-show="showAddCondiBtn"
  80. :disabled="disabled">
  81. 添加条件
  82. </el-button>
  83. </div>
  84. <el-button type="danger" @click="deleteConditionGroup" :icon="Delete" v-show="showDelGroupBtn">删除组</el-button>
  85. </div>
  86. <el-divider border-style="dashed" class="condition-group-divider"> 或 </el-divider>
  87. </template>
  88. <script lang="ts" setup>
  89. import { ref, reactive, computed, watch } from 'vue'
  90. import { Delete, Plus } from '@element-plus/icons-vue'
  91. import InputFloat from '/@/components/input-float/index.vue'
  92. import TagsInput from '/@/components/tags-input/index.vue'
  93. import RangeFloat from '/@/components/range-float/index.vue'
  94. import { useSymbolOptions } from './utils'
  95. import XEUtils from 'xe-utils'
  96. interface Props {
  97. candidateFields: CandidateField[]
  98. data: ConditionGroupItem
  99. showDelGroupBtn: boolean
  100. disabled: boolean
  101. }
  102. const emits = defineEmits(['deleteGroup'])
  103. const periodEnum = [
  104. { label: '昨天', value: 1 },
  105. { label: '过去7天', value: 7 },
  106. { label: '过去14天', value: 14 },
  107. { label: '过去30天', value: 30 },
  108. { label: '过去60天', value: 60 },
  109. { label: '过去90天', value: 90 },
  110. ]
  111. const excludeEnum = [
  112. { label: '不排除', value: 0 },
  113. { label: '昨天', value: 1 },
  114. { label: '最近2天', value: 2 },
  115. { label: '最近3天', value: 3 },
  116. { label: '最近7天', value: 7 },
  117. { label: '最近14天', value: 14 },
  118. ]
  119. const props = withDefaults(defineProps<Props>(), {
  120. disabled: false,
  121. })
  122. const formRef = ref()
  123. const formData = ref(props.data)
  124. const { getSymbolOptions, getFieldInfo } = useSymbolOptions(props.candidateFields)
  125. const getFormProp = (conditionInfo: ConditionItem, index: number) => {
  126. if (['between', 'not_between'].includes(conditionInfo.symbol)) {
  127. return `items[${index}].ranges`
  128. }
  129. if (['in', 'not_in'].includes(conditionInfo.symbol)) {
  130. return `items[${index}].values`
  131. }
  132. return `items[${index}].num`
  133. }
  134. const getFormTrigger = (conditionInfo: ConditionItem) => {
  135. const fieldInfo = getFieldInfo(conditionInfo.dataType)
  136. if (fieldInfo.options && fieldInfo.options.length > 0) return 'change'
  137. return 'blur'
  138. }
  139. const checkRight = (rule: any, value: string | string[], callback: any) => {
  140. // console.log(139, rule, value)
  141. if (rule.field.includes('num')) {
  142. if (XEUtils.toNumber(value) <= 0 ) {
  143. callback(new Error('请输入大于0的数值!'))
  144. }
  145. } else if (rule.field.includes('values')) {
  146. if (value.length === 0) {
  147. callback(new Error('必填项'))
  148. }
  149. } else if (rule.field.includes('ranges')) {
  150. for (const val of value) {
  151. if (XEUtils.toNumber(val) <= 0) {
  152. callback(new Error('请输入大于0的数值!'))
  153. break
  154. }
  155. }
  156. if (XEUtils.toNumber(value[0]) >= XEUtils.toNumber(value[1])) {
  157. callback(new Error('起始值必须小于终止值!'))
  158. }
  159. }
  160. callback()
  161. }
  162. const validate = async () => {
  163. let ret = false
  164. await formRef.value.validate((valid: any) => {
  165. ret = valid
  166. })
  167. return ret
  168. }
  169. const showAddCondiBtn = computed(() => {
  170. return formData.value.items.length < props.candidateFields.length
  171. })
  172. const selectedFields = computed(() => {
  173. const ret = {}
  174. for (const info of formData.value.items) {
  175. ret[info.dataType] = true
  176. }
  177. return ret
  178. })
  179. const buildConditionItem = (field: string) => {
  180. const symbol = getSymbolOptions(field)[0].value
  181. return {
  182. dataType: field,
  183. dayType: symbol === 'in' || symbol === 'not_in' ? '' : 'sum',
  184. symbol: symbol,
  185. num: '',
  186. ranges: [],
  187. values: [],
  188. }
  189. }
  190. const addConditionItem = () => {
  191. for (const item of props.candidateFields) {
  192. if (!selectedFields.value[item.value]) {
  193. formData.value.items.push(buildConditionItem(item.value))
  194. break
  195. }
  196. }
  197. }
  198. // const changeSymbol = (conditionInfo: ConditionItem) => {
  199. // if(conditionInfo.symbol === 'between' || conditionInfo.symbol === 'not_between') {
  200. // if (conditionInfo.values.length !== 2) {
  201. // conditionInfo.values.splice(0, conditionInfo.values.length)
  202. // }
  203. // }
  204. // }
  205. const changeDataType = (conditionInfo: ConditionItem) => {
  206. conditionInfo.symbol = getSymbolOptions(conditionInfo.dataType)[0].value
  207. conditionInfo.num = ''
  208. // conditionInfo.ranges[0] = ''
  209. // conditionInfo.ranges[1] = ''
  210. conditionInfo.ranges.splice(0, conditionInfo.ranges.length)
  211. conditionInfo.values.splice(0, conditionInfo.values.length)
  212. }
  213. const findPrefix = (dataType: string) => {
  214. return props.candidateFields.find((item) => item.value === dataType).prefix ?? ''
  215. }
  216. const findSuffix = (dataType: string) => {
  217. return props.candidateFields.find((item) => item.value === dataType).suffix ?? ''
  218. }
  219. const deleteCondition = (index: number) => {
  220. formData.value.items.splice(index, 1)
  221. }
  222. const deleteConditionGroup = () => {
  223. emits('deleteGroup')
  224. }
  225. watch(
  226. () => props.data,
  227. () => {
  228. formData.value = props.data
  229. },
  230. { deep: true }
  231. )
  232. defineExpose({ validate })
  233. </script>
  234. <style scoped>
  235. .condition-group-item-wrap {
  236. background: #f4f7fe;
  237. padding: 10px;
  238. display: flex;
  239. justify-content: space-between;
  240. align-items: center;
  241. /* position: relative; */
  242. }
  243. .and-span {
  244. display: inline-block;
  245. width: 20px;
  246. height: 20px;
  247. background: #fff;
  248. text-align: center;
  249. line-height: 20px;
  250. border-radius: 50%;
  251. margin: auto 0;
  252. }
  253. div.condition-group-divider:last-of-type {
  254. display: none;
  255. }
  256. </style>