index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <template>
  2. <div class="search-form">
  3. <!-- 使用 Form 组件 -->
  4. <Form ref="formRef" :config="searchConfig" :model-value="searchData" @update:model-value="handleFormUpdate"
  5. :inline="inline" :size="size" :label-width="labelWidth" v-bind="$attrs">
  6. <!-- 省市区域选择器 slot -->
  7. <template #areaSelect="{ value, formData }">
  8. <AddressSelect v-model="formData.areaIds" style="width: 180px" />
  9. </template>
  10. </Form>
  11. <!-- 操作按钮 -->
  12. <div v-if="showButtons" class="search-buttons">
  13. <el-button type="primary" :icon="Search" :loading="loading" @click="handleSearch">
  14. {{ searchText }}
  15. </el-button>
  16. <el-button :icon="Refresh" @click="handleReset">
  17. {{ resetText }}
  18. </el-button>
  19. <el-button v-if="showExpand" type="text" :icon="expandCollapse ? ArrowUp : ArrowDown" @click="toggleExpand">
  20. {{ expandCollapse ? "收起" : "展开" }}
  21. </el-button>
  22. </div>
  23. </div>
  24. </template>
  25. <script setup>
  26. import { ref, reactive, watch, computed, onMounted } from "vue";
  27. import { Search, Refresh, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
  28. import Form from "@/components/Form/index.vue";
  29. import AddressSelect from "@/components/AddressSelect/index.vue";
  30. const props = defineProps({
  31. // 表单配置(从父组件传入)
  32. config: {
  33. type: Array,
  34. default: () => [],
  35. },
  36. // 搜索数据
  37. modelValue: {
  38. type: Object,
  39. default: () => ({}),
  40. },
  41. // 是否内联
  42. inline: {
  43. type: Boolean,
  44. default: true,
  45. },
  46. // 尺寸
  47. size: {
  48. type: String,
  49. default: "default",
  50. },
  51. // 标签宽度
  52. labelWidth: {
  53. type: String,
  54. default: "auto",
  55. },
  56. // 是否显示按钮
  57. showButtons: {
  58. type: Boolean,
  59. default: true,
  60. },
  61. // 搜索按钮文本
  62. searchText: {
  63. type: String,
  64. default: "搜索",
  65. },
  66. // 重置按钮文本
  67. resetText: {
  68. type: String,
  69. default: "重置",
  70. },
  71. // 是否显示展开/收起
  72. showExpand: {
  73. type: Boolean,
  74. default: false,
  75. },
  76. // 展开时显示的字段数量
  77. expandCount: {
  78. type: Number,
  79. default: 3,
  80. },
  81. // 加载状态
  82. loading: {
  83. type: Boolean,
  84. default: false,
  85. },
  86. // 搜索处理函数(从父组件传入)
  87. onSearch: {
  88. type: Function,
  89. default: null,
  90. },
  91. // 重置处理函数(从父组件传入)
  92. onReset: {
  93. type: Function,
  94. default: null,
  95. },
  96. });
  97. const emit = defineEmits([
  98. "update:modelValue",
  99. "search",
  100. "reset",
  101. "expand-change",
  102. ]);
  103. const formRef = ref();
  104. const searchData = reactive({});
  105. const expandCollapse = ref(true);
  106. // 处理Form组件update:model-value事件
  107. const handleFormUpdate = (newData) => {
  108. Object.assign(searchData, newData);
  109. };
  110. // 监听父组件传入的modelValue变化,同步到searchData
  111. watch(
  112. () => props.modelValue,
  113. (newValue) => {
  114. if (newValue) {
  115. Object.assign(searchData, newValue);
  116. }
  117. },
  118. { immediate: true, deep: true }
  119. );
  120. // 监听searchData变化,同步回父组件
  121. watch(
  122. searchData,
  123. (newData) => {
  124. emit("update:modelValue", { ...newData });
  125. },
  126. { deep: true }
  127. );
  128. // 强制监听searchData中的areaIds字段
  129. watch(
  130. () => searchData.areaIds,
  131. (newAreaIds, oldAreaIds) => {
  132. if (newAreaIds !== oldAreaIds) {
  133. emit("update:modelValue", { ...searchData });
  134. }
  135. },
  136. { immediate: false, deep: true }
  137. );
  138. // 计算属性 - 筛选出可搜索的字段
  139. const searchConfig = computed(() => {
  140. let config = props.config.filter((item) => item.search !== false);
  141. if (props.showExpand && !expandCollapse.value) {
  142. config = config.slice(0, props.expandCount);
  143. }
  144. return config;
  145. });
  146. // 搜索处理
  147. const handleSearch = () => {
  148. const params = { ...searchData };
  149. // 过滤空值
  150. Object.keys(params).forEach((key) => {
  151. if (
  152. params[key] === "" ||
  153. params[key] === null ||
  154. params[key] === undefined
  155. ) {
  156. delete params[key];
  157. }
  158. });
  159. // 如果父组件传入了搜索处理函数,则调用
  160. if (props.onSearch) {
  161. props.onSearch(params);
  162. }
  163. emit("search", params);
  164. };
  165. // 重置处理
  166. const handleReset = () => {
  167. formRef.value.resetFields();
  168. // 如果父组件传入了重置处理函数,则调用
  169. if (props.onReset) {
  170. props.onReset();
  171. }
  172. emit("reset");
  173. emit("search", {});
  174. };
  175. // 展开/收起切换
  176. const toggleExpand = () => {
  177. expandCollapse.value = !expandCollapse.value;
  178. emit("expand-change", expandCollapse.value);
  179. };
  180. // 监听外部数据变化
  181. watch(
  182. () => props.modelValue,
  183. (newVal) => {
  184. Object.assign(searchData, newVal);
  185. },
  186. { deep: true, immediate: true }
  187. );
  188. // 监听搜索数据变化
  189. watch(
  190. searchData,
  191. (newVal) => {
  192. emit("update:modelValue", { ...newVal });
  193. },
  194. { deep: true }
  195. );
  196. // 暴露方法
  197. defineExpose({
  198. formRef,
  199. handleSearch,
  200. handleReset,
  201. toggleExpand,
  202. });
  203. </script>
  204. <style scoped>
  205. .search-form {
  206. background: var(--el-bg-color);
  207. padding: 18px;
  208. border-radius: 4px;
  209. margin-bottom: 16px;
  210. }
  211. .search-buttons {
  212. margin-top: 18px;
  213. text-align: left;
  214. }
  215. .search-buttons .el-button {
  216. margin-right: 10px;
  217. }
  218. </style>