Write.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <script setup lang="tsx">
  2. import { Form, FormSchema } from '@/components/Form'
  3. import { useForm } from '@/hooks/web/useForm'
  4. import { PropType, watch, ref, onMounted, unref } from 'vue'
  5. import { useValidator } from '@/hooks/web/useValidator'
  6. import { cloneDeep } from 'lodash-es'
  7. import { ElInputTag, ElAutocomplete } from 'element-plus'
  8. import { getMapSearch } from '@/api/system/admin'
  9. import { UpImgButtom } from '@/components/UpFile'
  10. import { mapAddressData } from '@/api/system/admin/types'
  11. import { getConfigKey } from '@/api/system/admin'
  12. import { UserButtom } from '@/components/UserList'
  13. const { required, phone } = useValidator()
  14. const mapKey = ref('')
  15. onMounted(async () => {
  16. const res = await getConfigKey('tengxun_map_key')
  17. if (res) {
  18. mapKey.value = res.data.tengxun_map_key
  19. }
  20. })
  21. const props = defineProps({
  22. currentRow: {
  23. type: Object as PropType<any>,
  24. default: () => null
  25. }
  26. })
  27. const formSchema = ref<FormSchema[]>([
  28. {
  29. field: 'uid',
  30. label: '用户',
  31. component: 'CheckboxGroup',
  32. formItemProps: {
  33. slots: {
  34. default: (data) => {
  35. return (
  36. <>
  37. <UserButtom
  38. v-model={data.uid}
  39. onChangeUser={(res) => {
  40. checkedUser(res)
  41. }}
  42. ></UserButtom>
  43. </>
  44. )
  45. }
  46. }
  47. }
  48. },
  49. {
  50. field: 'name',
  51. label: '姓名',
  52. component: 'Input',
  53. componentProps: {
  54. placeholder: '请输入姓名'
  55. }
  56. },
  57. {
  58. field: 'avatar',
  59. label: '头像',
  60. component: 'CheckboxGroup',
  61. componentProps: {
  62. placeholder: '选择头像'
  63. },
  64. formItemProps: {
  65. slots: {
  66. default: (data) => {
  67. return (
  68. <>
  69. <UpImgButtom v-model={data.avatar}></UpImgButtom>
  70. </>
  71. )
  72. }
  73. }
  74. }
  75. },
  76. {
  77. field: 'phone',
  78. label: '手机号',
  79. component: 'Input',
  80. componentProps: {
  81. type: 'number',
  82. placeholder: '请输入手机号'
  83. }
  84. },
  85. {
  86. field: 'gender',
  87. label: '性别',
  88. component: 'Select',
  89. value: 0,
  90. componentProps: {
  91. placeholder: '请选择性别',
  92. options: [
  93. {
  94. label: '保密',
  95. value: 0
  96. },
  97. {
  98. label: '男',
  99. value: 1
  100. },
  101. {
  102. label: '女',
  103. value: 2
  104. }
  105. ]
  106. }
  107. },
  108. {
  109. field: 'birth_day_time',
  110. label: '出生日期',
  111. component: 'DatePicker'
  112. },
  113. {
  114. field: 'start_job_year',
  115. label: '就业年份',
  116. component: 'Input',
  117. componentProps: {
  118. placeholder: '例子:2014'
  119. }
  120. },
  121. {
  122. field: 'tag_list',
  123. label: '标签',
  124. colProps: {
  125. span: 24
  126. },
  127. component: 'CheckboxGroup',
  128. formItemProps: {
  129. slots: {
  130. default: (data) => {
  131. return (
  132. <>
  133. <ElInputTag
  134. v-model={data.tag_list}
  135. tagEffect="light"
  136. tagType="primary"
  137. clearable
  138. ></ElInputTag>
  139. </>
  140. )
  141. }
  142. }
  143. }
  144. },
  145. {
  146. field: 'detail_address',
  147. label: '省市区地址',
  148. colProps: {
  149. span: 24
  150. },
  151. component: 'Input',
  152. formItemProps: {
  153. slots: {
  154. default: (data) => {
  155. return (
  156. <>
  157. <ElAutocomplete
  158. v-model={data.detail_address}
  159. value-key="label"
  160. onSelect={(val: any) => {
  161. selectAddress(val)
  162. }}
  163. fetch-suggestions={(val, cb) => {
  164. searchAddress(val, cb)
  165. }}
  166. >
  167. {{
  168. default: ({ item }) => {
  169. return (
  170. <>
  171. <div class="lh-none mt-10px">{item.label}</div>
  172. <div class="font-size-12px lh-none mt-5px pb-10px b-b-solid b-b-light b-b-1px">
  173. {item.address}
  174. </div>
  175. </>
  176. )
  177. }
  178. }}
  179. </ElAutocomplete>
  180. </>
  181. )
  182. }
  183. }
  184. }
  185. },
  186. {
  187. field: 'province',
  188. label: '省份',
  189. component: 'Input',
  190. hidden: true
  191. },
  192. {
  193. field: 'city',
  194. label: '市',
  195. hidden: true,
  196. component: 'Input'
  197. },
  198. {
  199. field: 'area',
  200. label: '区',
  201. hidden: true,
  202. component: 'Input'
  203. },
  204. {
  205. field: 'longitude',
  206. label: '经度',
  207. hidden: true,
  208. component: 'Input'
  209. },
  210. {
  211. field: 'latitude',
  212. label: '纬度',
  213. hidden: true,
  214. component: 'Input'
  215. },
  216. {
  217. field: 'id',
  218. hidden: true,
  219. component: 'Input'
  220. }
  221. ])
  222. const rules = ref({
  223. uid: [required('请选择用户')],
  224. avatar: [required('请上传头像')],
  225. phone: [required('请填写手机号'), phone()],
  226. birth_day_time: [required('请选择出生日期')],
  227. detail_address: [
  228. {
  229. required: true,
  230. validator: (_, val, callback) => {
  231. if (!actionAddress.value || !val) {
  232. callback(new Error('请选择地址'))
  233. } else {
  234. callback()
  235. }
  236. }
  237. }
  238. ],
  239. name: [required('请填写等级名称')]
  240. })
  241. const { formRegister, formMethods } = useForm()
  242. const { setValues, getFormData, getElFormExpose } = formMethods
  243. const submit = async () => {
  244. const elForm = await getElFormExpose()
  245. const valid = await elForm?.validate().catch((err) => {
  246. console.log(err)
  247. })
  248. if (valid) {
  249. const formData = await getFormData()
  250. return formData
  251. }
  252. }
  253. defineExpose({
  254. submit
  255. })
  256. const checkedUser = async (res: any) => {
  257. setValues({
  258. uid: res.uid,
  259. name: res.real_name,
  260. avatar: [res.avatar],
  261. birth_day_time: res.birthday,
  262. phone: res.phone,
  263. gender: res.sex
  264. })
  265. }
  266. const actionAddress = ref<mapAddressData>()
  267. const searchAddress = async (queryString: string, cb: (arg: any) => void) => {
  268. if (queryString) {
  269. const { data } = await getMapSearch({
  270. key: unref(mapKey),
  271. keyword: queryString
  272. })
  273. cb(
  274. data.map((item): mapAddressData => {
  275. return {
  276. adcode: item.adcode,
  277. label: item.title,
  278. address: item.address,
  279. lng: item.location.lng,
  280. lat: item.location.lat,
  281. province: item.province,
  282. city: item.city,
  283. district: item.district
  284. }
  285. })
  286. )
  287. }
  288. }
  289. const selectAddress = (item: mapAddressData) => {
  290. if (!item || !item.adcode || !item.lng || !item.lat) {
  291. console.error('Invalid input item:', item)
  292. return
  293. }
  294. const adcodeStr = String(item.adcode).padStart(6, '0') // 确保 adcode 至少有6位
  295. const province = adcodeStr.slice(0, 2)
  296. const city = adcodeStr.slice(2, 4)
  297. const area = adcodeStr
  298. setValues({
  299. province: `${province}`,
  300. city: `${province}${city}00000000`,
  301. area: `${area}000000`,
  302. longitude: item.lng,
  303. latitude: item.lat
  304. })
  305. actionAddress.value = item
  306. center.value = {
  307. lng: item.lng,
  308. lat: item.lat
  309. }
  310. geometries.value = [{ styleId: 'marker', position: { lat: item.lat, lng: item.lng } }]
  311. }
  312. const center = ref({ lat: 28.655759, lng: 121.420808 })
  313. const geometries = ref([{ styleId: 'marker', position: { lat: 28.655759, lng: 121.420808 } }])
  314. watch(
  315. () => props.currentRow,
  316. async (value) => {
  317. if (!value) return
  318. const currentRow = cloneDeep(value)
  319. actionAddress.value = {
  320. adcode: currentRow.area,
  321. label: currentRow.detail_address,
  322. lng: currentRow.longitude,
  323. lat: currentRow.latitude
  324. }
  325. setValues(currentRow)
  326. },
  327. {
  328. deep: true,
  329. immediate: true
  330. }
  331. )
  332. </script>
  333. <template>
  334. <div class="flex flex-col">
  335. <Form :rules="rules" @register="formRegister" :schema="formSchema" />
  336. <TlbsMap
  337. v-if="mapKey"
  338. ref="map"
  339. :api-key="mapKey"
  340. :zoom="15"
  341. :center="center"
  342. :control="{
  343. scale: {},
  344. zoom: {
  345. position: 'topRight'
  346. }
  347. }"
  348. >
  349. <TlbsMultiMarker
  350. :geometries="geometries"
  351. :styles="{
  352. marker: {
  353. width: 20,
  354. height: 30,
  355. anchor: { x: 10, y: 30 }
  356. }
  357. }"
  358. />
  359. </TlbsMap>
  360. </div>
  361. </template>
  362. <style lang="less" scoped>
  363. :deep(.el-table__row) {
  364. .list {
  365. width: 50px;
  366. text-align: center;
  367. }
  368. }
  369. </style>