阅行客电子档案
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.

462 lines
15 KiB

2 weeks ago
2 weeks ago
  1. <template>
  2. <el-dialog
  3. :visible.sync="visible"
  4. title="高级检索"
  5. width="1000px"
  6. :close-on-click-modal="false"
  7. :modal-append-to-body="false"
  8. append-to-body
  9. :before-close="handleClose"
  10. >
  11. <div class="advanced-search-modal">
  12. <el-form ref="form" inline :model="form" :rules="rules" size="small" label-width="70px">
  13. <el-form-item label="字段名" prop="field">
  14. <el-select v-model="form.field" value-key="id" style="width: 200px;">
  15. <el-option v-for="item in fieldOptions" :key="item.id" :label="item.label" :value="item" />
  16. </el-select>
  17. </el-form-item>
  18. <el-form-item label="运算符" prop="symbol">
  19. <el-select v-model="form.symbol" value-key="value" placeholder="请选择" style="width: 200px;">
  20. <el-option
  21. v-for="item in symbolOptions"
  22. :key="item.value"
  23. :label="item.label"
  24. :value="item"
  25. />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item label="检索值" prop="keyWord" :rules="getKeywordRules">
  29. <el-input v-model="form.keyWord" :type="inputType" style="width: 200px;" />
  30. </el-form-item>
  31. </el-form>
  32. <div class="advanced-btn">
  33. <el-button size="mini" @click="addConditionData"><i class="iconfont icon-xinzeng" />新增</el-button>
  34. <el-button class="filter-refresh" size="mini" icon="el-icon-refresh-left" @click="resetQuery">重置</el-button>
  35. </div>
  36. <div class="search-condition">
  37. <h4> 检索条件</h4>
  38. <div class="condition-main">
  39. <div class="condition-left">
  40. <el-button size="mini" :disabled="currentIndex===null" @click="deltCurrent(currentIndex)"><i class="iconfont icon-shanchu" />删除</el-button>
  41. <el-button size="mini" icon="el-icon-top" :disabled="currentIndex === 0" @click="moveUp(currentIndex)">上移</el-button>
  42. <el-button size="mini" icon="el-icon-bottom" :disabled="currentIndex === conditionData.length - 1" @click="moveDown(currentIndex)">下移</el-button>
  43. </div>
  44. <ul id="condition-container-modal" class="condition-content">
  45. <li v-for="(item, index) in conditionData" :id="'modal-element-id-' + index" :key="index" :class="currentIndex===index ? 'active': ''" @click="selectCurrent(index)">
  46. <span style="color:#0348F3">{{ item.field }}</span>
  47. <span style="color:#ED4A41; margin:0 4px">{{ item.symbol }}</span>
  48. <span v-if="item.symbol && (item.symbol === '包含'|| item.symbol === '不包含')" class="keyword-style"><i>'%</i>{{ item.keyWord }}<i>%'</i></span>
  49. <span v-else-if="item.keyWord && isNaN(parseInt(item.keyWord))" class="keyword-style"><i>'</i>{{ item.keyWord }}<i>'</i></span>
  50. <span v-else class="keyword-style">{{ item.keyWord }}</span>
  51. <span>{{ item.connector }}</span>
  52. <span>{{ item.bracket }}</span>
  53. </li>
  54. </ul>
  55. <div class="condition-right">
  56. <el-button v-for="(item,index) in connectorList" :key="index" type="primary" size="mini" @click="addConnector(item)">{{ item }}</el-button>
  57. </div>
  58. </div>
  59. </div>
  60. </div>
  61. <div slot="footer" class="dialog-footer">
  62. <el-button @click="handleClose">取消</el-button>
  63. <el-button type="primary" @click="handleSearch">检索</el-button>
  64. </div>
  65. </el-dialog>
  66. </template>
  67. <script>
  68. import { FetchInitCategoryInputFieldByPid } from '@/api/system/category/category'
  69. export default {
  70. name: 'AdvancedSearchModal',
  71. props: {
  72. visible: {
  73. type: Boolean,
  74. default: false
  75. },
  76. selectedCategory: {
  77. type: Object,
  78. default: () => ({})
  79. },
  80. collectLevel: {
  81. type: Number,
  82. default: 0
  83. },
  84. initialConditions: {
  85. type: Array,
  86. default: () => []
  87. }
  88. },
  89. data() {
  90. return {
  91. form: {
  92. field: null,
  93. symbol: null,
  94. keyWord: null
  95. },
  96. fieldOptions: [],
  97. symbolOptions: [
  98. {
  99. label: '包含',
  100. value: 'like'
  101. },
  102. {
  103. label: '不包含',
  104. value: 'not like'
  105. },
  106. {
  107. label: '等于',
  108. value: '='
  109. },
  110. {
  111. label: '不等于',
  112. value: '!='
  113. },
  114. {
  115. label: '为空',
  116. value: 'is null'
  117. },
  118. {
  119. label: '不为空',
  120. value: 'is not null'
  121. },
  122. {
  123. label: '大于',
  124. value: '>'
  125. },
  126. {
  127. label: '大于等于',
  128. value: '>='
  129. },
  130. {
  131. label: '小于',
  132. value: '<'
  133. },
  134. {
  135. label: '小于等于',
  136. value: '<='
  137. }
  138. ],
  139. rules: {
  140. field: [
  141. { required: true, message: '请选择字段名', trigger: 'change' }
  142. ],
  143. symbol: [
  144. { required: true, message: '请选择运算符', trigger: 'change' }
  145. ]
  146. },
  147. conditionData: [],
  148. currentIndex: null,
  149. connectorList: ['并且', '或者', '(', ')']
  150. }
  151. },
  152. computed: {
  153. getKeywordRules() {
  154. if ((this.form.symbol && this.form.symbol.label === '为空') || (this.form.symbol && this.form.symbol.label === '不为空')) {
  155. return []
  156. } else {
  157. return [{ required: true, message: '请输入检索值', trigger: 'blur' }]
  158. }
  159. },
  160. inputType() {
  161. if (
  162. this.form.symbol &&
  163. (this.form.symbol.label === '大于' ||
  164. this.form.symbol.label === '大于等于' ||
  165. this.form.symbol.label === '小于' ||
  166. this.form.symbol.label === '小于等于')
  167. ) {
  168. return 'number'
  169. } else {
  170. return 'text'
  171. }
  172. }
  173. },
  174. watch: {
  175. visible(newVal) {
  176. if (!newVal) {
  177. this.resetQuery()
  178. this.conditionData = []
  179. this.currentIndex = null
  180. } else if (this.initialConditions && this.initialConditions.length > 0) {
  181. this.conditionData = JSON.parse(JSON.stringify(this.initialConditions))
  182. }
  183. }
  184. },
  185. mounted() {
  186. this.getFieldCommon()
  187. },
  188. methods: {
  189. resetQuery() {
  190. if (this.$refs.form) {
  191. this.$refs.form.resetFields()
  192. }
  193. },
  194. addConditionData() {
  195. this.$refs.form.validate((valid) => {
  196. if (valid) {
  197. const newConditionData = {}
  198. newConditionData.field = this.form.field.label
  199. newConditionData.fieldName = this.form.field.value
  200. newConditionData.symbol = this.form.symbol.label
  201. newConditionData.symbolCode = this.form.symbol.value
  202. newConditionData.keyWord = this.form.keyWord
  203. this.conditionData.push(newConditionData)
  204. this.$nextTick(() => {
  205. const container = document.getElementById('condition-container-modal')
  206. container.scrollTop = container.scrollHeight
  207. })
  208. }
  209. })
  210. },
  211. moveUp(index) {
  212. if (index > 0) {
  213. const temp = this.conditionData[index]
  214. this.conditionData[index] = this.conditionData[index - 1]
  215. this.conditionData[index - 1] = temp
  216. this.currentIndex = index - 1
  217. }
  218. const targetElement = document.getElementById('modal-element-id-' + this.currentIndex)
  219. if (targetElement) {
  220. targetElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
  221. }
  222. },
  223. moveDown(index) {
  224. if (index < this.conditionData.length - 1) {
  225. const temp = this.conditionData[index]
  226. this.conditionData[index] = this.conditionData[index + 1]
  227. this.conditionData[index + 1] = temp
  228. this.currentIndex = index + 1
  229. }
  230. const targetElement = document.getElementById('modal-element-id-' + this.currentIndex)
  231. if (targetElement) {
  232. targetElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
  233. }
  234. },
  235. deltCurrent(index) {
  236. this.conditionData.splice(index, 1)
  237. this.currentIndex = null
  238. },
  239. selectCurrent(index) {
  240. if (this.currentIndex === index) {
  241. this.currentIndex = null
  242. } else {
  243. this.currentIndex = index
  244. }
  245. },
  246. addConnector(item) {
  247. const newConditionData = {}
  248. if (item === '并且' || item === '或者') {
  249. newConditionData.connector = item
  250. } else {
  251. newConditionData.bracket = item
  252. }
  253. this.conditionData.push(newConditionData)
  254. this.$nextTick(() => {
  255. const container = document.getElementById('condition-container-modal')
  256. container.scrollTop = container.scrollHeight
  257. })
  258. },
  259. getFieldCommon() {
  260. const params = {
  261. 'categoryId': this.selectedCategory.id,
  262. 'categoryLevel': this.collectLevel
  263. }
  264. // console.log('params', params)
  265. FetchInitCategoryInputFieldByPid(params).then((data) => {
  266. if (data && data.length > 0) {
  267. this.fieldOptions = data
  268. .filter(item => item.fieldName !== 'fonds_no' && item.fieldName !== 'fonds_name')
  269. .map(item => {
  270. return {
  271. id: item.id,
  272. label: item.fieldCnName,
  273. value: item.fieldName
  274. }
  275. })
  276. }
  277. })
  278. },
  279. checkConditions(conditionData) {
  280. let brackets = 0
  281. let fields = 0
  282. let connectors = 0
  283. let previousTokenType = null
  284. let hasValidConditionBetweenBrackets = false
  285. for (var i = 0; i < conditionData.length; i++) {
  286. const condition = conditionData[i]
  287. let currentTokenType = ''
  288. if (condition.hasOwnProperty('bracket')) {
  289. currentTokenType = 'bracket'
  290. brackets++
  291. } else if (condition.hasOwnProperty('field')) {
  292. currentTokenType = 'field'
  293. fields++
  294. if (brackets > 0) {
  295. hasValidConditionBetweenBrackets = true
  296. }
  297. } else if (condition.hasOwnProperty('connector')) {
  298. currentTokenType = 'connector'
  299. connectors++
  300. }
  301. if (previousTokenType && currentTokenType) {
  302. if ((previousTokenType === 'field' && currentTokenType === 'field') ||
  303. (previousTokenType === 'connector' && currentTokenType === 'connector')) {
  304. this.$message({ message: '条件之间缺少或且连接符', type: 'error', offset: 8 })
  305. return null
  306. }
  307. }
  308. previousTokenType = currentTokenType
  309. }
  310. if (brackets > 0 && !hasValidConditionBetweenBrackets) {
  311. this.$message({ message: '请输入有效条件', type: 'error', offset: 8 })
  312. return null
  313. } else if (brackets > 0 && brackets % 2 !== 0) {
  314. this.$message({ message: '括号不对称', type: 'error', offset: 8 })
  315. return null
  316. } else if (fields === 0) {
  317. this.$message({ message: '请输入有效条件', type: 'error', offset: 8 })
  318. return null
  319. } else if (fields === 1 || connectors === fields - 1) {
  320. const wheresql = this.conditionData.map(obj => {
  321. if (obj.field) {
  322. if (obj.symbol === '包含' || obj.symbol === '不包含') {
  323. return obj.fieldName + ' ' + obj.symbolCode + " '%" + obj.keyWord + "%'"
  324. } else if (obj.keyWord && isNaN(parseInt(obj.keyWord))) {
  325. return obj.fieldName + ' ' + obj.symbolCode + " '" + obj.keyWord + "'"
  326. } else {
  327. return obj.fieldName + ' ' + obj.symbolCode + ' ' + obj.keyWord
  328. }
  329. } else if (obj.connector === '并且') {
  330. return 'and'
  331. } else if (obj.connector === '或者') {
  332. return 'or'
  333. } else {
  334. return obj.bracket
  335. }
  336. }).join(' ')
  337. return wheresql
  338. } else {
  339. this.$message({ message: '条件之间缺少或且连接符', type: 'error', offset: 8 })
  340. return null
  341. }
  342. },
  343. handleSearch() {
  344. const wheresql = this.checkConditions(this.conditionData)
  345. // console.log('wheresql', wheresql)
  346. if (wheresql) {
  347. const conditions = JSON.parse(JSON.stringify(this.conditionData))
  348. // 保存到 localStorage
  349. localStorage.setItem('advancedSearchConditions', JSON.stringify(conditions))
  350. localStorage.setItem('advancedSearchSql', wheresql)
  351. // 发送 SQL 条件和原始条件数据(用于显示文案)
  352. this.$emit('search', {
  353. sql: wheresql,
  354. conditions: conditions
  355. })
  356. }
  357. },
  358. handleClose() {
  359. this.$emit('update:visible', false)
  360. }
  361. }
  362. }
  363. </script>
  364. <style lang='scss' scoped>
  365. .advanced-search-modal {
  366. .el-form--inline .el-form-item {
  367. margin-right: 20px !important;
  368. }
  369. .advanced-btn {
  370. display: flex;
  371. justify-content: center;
  372. margin-top: 15px;
  373. .el-button {
  374. margin-right: 10px;
  375. }
  376. }
  377. .search-condition {
  378. padding: 18px;
  379. margin-top: 20px;
  380. background: #F6F9FF;
  381. border-radius: 3px;
  382. border: 1px dashed #DCDFE6;
  383. h4 {
  384. margin: 0 0 17px 0;
  385. padding-left: 25px;
  386. color: #0C0E1E;
  387. font-size: 14px;
  388. font-weight: bold;
  389. }
  390. }
  391. .condition-main {
  392. display: flex;
  393. justify-content: center;
  394. flex-wrap: nowrap;
  395. .condition-left {
  396. display: flex;
  397. flex-direction: column;
  398. justify-content: center;
  399. .el-button {
  400. width: 76px;
  401. margin: 5px 0;
  402. ::v-deep i.el-icon-top,
  403. ::v-deep i.el-icon-bottom {
  404. font-size: 16px;
  405. font-weight: bold;
  406. }
  407. }
  408. }
  409. .condition-content {
  410. width: 500px;
  411. height: 160px;
  412. margin: 0 10px;
  413. padding: 0;
  414. background: #E6E8ED;
  415. overflow: hidden;
  416. overflow-y: scroll;
  417. li {
  418. display: flex;
  419. justify-content: center;
  420. flex-wrap: nowrap;
  421. height: 32px;
  422. line-height: 32px;
  423. font-size: 14px;
  424. text-align: center;
  425. background-color: #fff;
  426. border-bottom: 1px solid #E6E8ED;
  427. cursor: default;
  428. &:hover,
  429. &:focus,
  430. &.active {
  431. background-color: #E8F2FF;
  432. }
  433. .keyword-style {
  434. color: #2ECAAC;
  435. i {
  436. font-style: normal;
  437. color: #545B65;
  438. }
  439. }
  440. }
  441. }
  442. .condition-right {
  443. display: flex;
  444. flex-direction: column;
  445. justify-content: center;
  446. .el-button {
  447. width: 64px;
  448. margin: 5px 0;
  449. background-color: #0348F3;
  450. color: #fff;
  451. }
  452. }
  453. }
  454. }
  455. </style>