Write.vue 8.9 KB

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