AddPetDialog.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <el-dialog :model-value="visible" @update:model-value="$emit('update:visible', $event)" title="新增宠物" width="800px"
  3. destroy-on-close append-to-body>
  4. <el-tabs v-model="activeTab">
  5. <el-tab-pane label="基本信息" name="basic">
  6. <el-form :model="form" label-width="100px">
  7. <el-row>
  8. <el-col :span="24" style="display: flex; justify-content: center; margin-bottom: 20px">
  9. <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false"
  10. :on-change="handleUploadFile">
  11. <el-avatar v-if="avatarDisplayUrl" :src="avatarDisplayUrl" :size="80" />
  12. <el-icon v-else class="avatar-uploader-icon">
  13. <Plus />
  14. </el-icon>
  15. </el-upload>
  16. </el-col>
  17. <el-col :span="12">
  18. <el-form-item label="宠物姓名" required><el-input v-model="form.name" /></el-form-item>
  19. </el-col>
  20. <el-col :span="12">
  21. <el-form-item label="所属主人" required>
  22. <el-select v-model="form.userId" placeholder="选择主人" style="width: 100%" filterable disabled>
  23. <el-option v-for="user in userOptions" :key="user.id" :label="user.name" :value="user.id" />
  24. </el-select>
  25. </el-form-item>
  26. </el-col>
  27. <el-col :span="12">
  28. <el-form-item label="性别">
  29. <el-select v-model="form.gender" placeholder="请选择">
  30. <el-option v-for="dict in sys_pet_gender" :key="dict.value" :label="dict.label"
  31. :value="parseInt(dict.value)" />
  32. </el-select>
  33. </el-form-item>
  34. </el-col>
  35. <el-col :span="12">
  36. <el-form-item label="品种" required>
  37. <el-input v-model="form.breed" placeholder="请输入品种" style="width: 100%" />
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="12">
  41. <el-form-item label="体型" required>
  42. <el-select v-model="form.size" style="width: 100%">
  43. <el-option v-for="dict in sys_pet_size" :key="dict.value" :label="dict.label" :value="dict.value" />
  44. </el-select>
  45. </el-form-item>
  46. </el-col>
  47. <el-col :span="12">
  48. <el-form-item label="体重(kg)" required><el-input-number v-model="form.weight" :min="0" :precision="1"
  49. style="width: 100%" /></el-form-item>
  50. </el-col>
  51. <el-col :span="12">
  52. <el-form-item label="年龄(岁)" required><el-input-number v-model="form.age" :min="0"
  53. style="width: 100%" /></el-form-item>
  54. </el-col>
  55. <el-col :span="24">
  56. <el-form-item label="性格关键词"><el-input v-model="form.personality" placeholder="如:活泼、粘人" /></el-form-item>
  57. </el-col>
  58. <el-col :span="24">
  59. <el-form-item label="萌宠性格"><el-input v-model="form.cutePersonality" type="textarea"
  60. placeholder="详细描述" /></el-form-item>
  61. </el-col>
  62. <el-col :span="24">
  63. <el-form-item label="宠物标签">
  64. <el-select v-model="form.tagIds" multiple placeholder="选择标签" style="width: 100%">
  65. <el-option v-for="tag in allPetTags" :key="tag.id" :label="tag.name" :value="tag.id">
  66. <el-tag :type="tag.colorType || 'info'" effect="light" size="small">{{ tag.name }}</el-tag>
  67. </el-option>
  68. </el-select>
  69. </el-form-item>
  70. </el-col>
  71. </el-row>
  72. </el-form>
  73. </el-tab-pane>
  74. <el-tab-pane label="家庭信息" name="family">
  75. <el-form :model="form" label-width="120px">
  76. <el-form-item label="新来家庭时间">
  77. <el-date-picker v-model="form.arrivalTime" type="date" placeholder="选择日期" style="width: 100%" />
  78. </el-form-item>
  79. <el-form-item label="家庭房屋类型" required>
  80. <el-radio-group v-model="form.houseType">
  81. <el-radio v-for="dict in sys_house_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
  82. </el-radio-group>
  83. </el-form-item>
  84. <el-form-item label="入门方式" required>
  85. <el-radio-group v-model="form.entryMethod">
  86. <el-radio v-for="dict in sys_entry_method" :key="dict.value" :value="dict.value">{{ dict.label
  87. }}</el-radio>
  88. </el-radio-group>
  89. </el-form-item>
  90. <el-form-item label="密码" v-if="form.entryMethod === 'password'" required>
  91. <el-input v-model="form.entryPassword" placeholder="请输入门锁密码" />
  92. </el-form-item>
  93. <el-form-item label="钥匙位置" v-if="form.entryMethod === 'key'" required>
  94. <el-input v-model="form.keyLocation" placeholder="请输入钥匙存放位置" />
  95. </el-form-item>
  96. </el-form>
  97. </el-tab-pane>
  98. <el-tab-pane label="健康状况" name="health">
  99. <el-form :model="form" label-width="120px">
  100. <el-form-item label="健康状态" required>
  101. <el-radio-group v-model="form.healthStatus">
  102. <el-radio value="健康">健康</el-radio>
  103. <el-radio value="亚健康">亚健康</el-radio>
  104. <el-radio value="疾病">疾病</el-radio>
  105. </el-radio-group>
  106. </el-form-item>
  107. <el-form-item label="是否有攻击倾向" required>
  108. <el-switch v-model="form.aggression" active-text="是" inactive-text="否" :active-value="1"
  109. :inactive-value="0" />
  110. </el-form-item>
  111. <el-form-item label="疫苗情况" required>
  112. <el-radio-group v-model="form.vaccineStatus">
  113. <el-radio value="无">无</el-radio>
  114. <el-radio value="已打1次">已打1次</el-radio>
  115. <el-radio value="已打2次">已打2次</el-radio>
  116. <el-radio value="已打3次">已打3次</el-radio>
  117. </el-radio-group>
  118. </el-form-item>
  119. <el-form-item label="疫苗凭证">
  120. <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false"
  121. :on-change="handleUploadVaccineCert">
  122. <img v-if="vaccineCertDisplayUrl" :src="vaccineCertDisplayUrl" class="avatar"
  123. style="width: 100px; height: 100px; object-fit: cover" />
  124. <el-icon v-else class="avatar-uploader-icon" style="width: 100px; height: 100px; line-height: 100px">
  125. <Plus />
  126. </el-icon>
  127. </el-upload>
  128. </el-form-item>
  129. <el-form-item label="既往病史" required>
  130. <el-input v-model="form.medicalHistory" type="textarea" placeholder="如有病史请记录" />
  131. </el-form-item>
  132. <el-form-item label="过敏史" required>
  133. <el-input v-model="form.allergies" type="textarea" placeholder="如有过敏源请记录" />
  134. </el-form-item>
  135. </el-form>
  136. </el-tab-pane>
  137. </el-tabs>
  138. <template #footer>
  139. <span class="dialog-footer">
  140. <el-button @click="$emit('update:visible', false)">取消</el-button>
  141. <el-button type="primary" :loading="submitLoading" @click="saveData">保存</el-button>
  142. </span>
  143. </template>
  144. </el-dialog>
  145. </template>
  146. <script setup>
  147. import { ref, reactive, watch, onMounted, getCurrentInstance, toRefs } from 'vue'
  148. import { ElMessage } from 'element-plus'
  149. import { globalHeaders } from '@/utils/request'
  150. import { addPetOnOrder } from '@/api/archieves/pet'
  151. import { getCustomer } from '@/api/archieves/customer'
  152. import { listAllTag } from '@/api/archieves/tag'
  153. const props = defineProps({
  154. visible: { type: Boolean, default: false },
  155. userId: { type: [String, Number], default: '' },
  156. userOptions: { type: Array, default: () => [] }
  157. })
  158. const emit = defineEmits(['update:visible', 'success'])
  159. const { proxy } = getCurrentInstance()
  160. const { sys_pet_gender, sys_pet_type, sys_pet_size, sys_house_type, sys_entry_method } = toRefs(
  161. proxy?.useDict('sys_pet_gender', 'sys_pet_type', 'sys_pet_size', 'sys_house_type', 'sys_entry_method')
  162. )
  163. const activeTab = ref('basic')
  164. const submitLoading = ref(false)
  165. const allPetTags = ref([])
  166. const avatarDisplayUrl = ref('')
  167. const vaccineCertDisplayUrl = ref('')
  168. const baseUrl = import.meta.env.VITE_APP_BASE_API
  169. const uploadUrl = baseUrl + '/resource/oss/upload'
  170. const form = reactive({
  171. userId: undefined,
  172. avatar: undefined,
  173. name: '',
  174. type: 0,
  175. gender: undefined,
  176. breed: '',
  177. birthday: '',
  178. age: 1,
  179. weight: 5,
  180. size: 'small',
  181. isSterilized: 0,
  182. arrivalTime: '',
  183. houseType: '',
  184. entryMethod: '',
  185. entryPassword: '',
  186. keyLocation: '',
  187. personality: '',
  188. cutePersonality: '',
  189. healthStatus: '健康',
  190. aggression: 0,
  191. vaccineStatus: '无',
  192. vaccineCert: undefined,
  193. medicalHistory: '',
  194. allergies: '',
  195. remark: '',
  196. tagIds: []
  197. })
  198. watch(() => props.visible, (val) => {
  199. if (val) {
  200. activeTab.value = 'basic'
  201. submitLoading.value = false
  202. avatarDisplayUrl.value = ''
  203. vaccineCertDisplayUrl.value = ''
  204. Object.assign(form, {
  205. userId: props.userId, avatar: undefined, name: '', type: 0, gender: undefined,
  206. breed: '', birthday: '', age: 1, weight: 5, size: 'small', isSterilized: 0,
  207. arrivalTime: '', houseType: '', entryMethod: '', entryPassword: '', keyLocation: '',
  208. personality: '', cutePersonality: '', healthStatus: '健康', aggression: 0,
  209. vaccineStatus: '无', vaccineCert: undefined, medicalHistory: '', allergies: '', remark: '', tagIds: []
  210. })
  211. // 自动从主人信息填充家庭信息重叠字段
  212. if (props.userId) {
  213. fetchAndFillOwnerInfo(props.userId)
  214. }
  215. }
  216. })
  217. const fetchAndFillOwnerInfo = (userId) => {
  218. getCustomer(userId).then((res) => {
  219. const data = res.data
  220. if (data) {
  221. form.houseType = data.houseType || form.houseType
  222. form.entryMethod = data.entryMethod || form.entryMethod
  223. form.entryPassword = data.entryPassword || form.entryPassword
  224. form.keyLocation = data.keyLocation || form.keyLocation
  225. }
  226. }).catch(() => {
  227. // 获取主人信息失败不影响新增宠物流程
  228. })
  229. }
  230. const loadTags = () => {
  231. listAllTag({ category: 'pet', status: 0 }).then((res) => {
  232. allPetTags.value = res.data || []
  233. })
  234. }
  235. const handleUploadFile = async (file) => {
  236. const formData = new FormData()
  237. formData.append('file', file.raw)
  238. try {
  239. const headers = globalHeaders()
  240. const res = await fetch(uploadUrl, {
  241. method: 'POST',
  242. headers: {
  243. 'Authorization': headers.Authorization,
  244. 'clientid': headers.clientid
  245. },
  246. body: formData
  247. })
  248. const result = await res.json()
  249. if (result.code === 200) {
  250. form.avatar = result.data.ossId
  251. avatarDisplayUrl.value = result.data.url
  252. } else {
  253. ElMessage.error(result.msg || '头像上传失败')
  254. }
  255. } catch (e) {
  256. ElMessage.error('头像上传失败')
  257. }
  258. }
  259. const handleUploadVaccineCert = async (file) => {
  260. const formData = new FormData()
  261. formData.append('file', file.raw)
  262. try {
  263. const headers = globalHeaders()
  264. const res = await fetch(uploadUrl, {
  265. method: 'POST',
  266. headers: {
  267. 'Authorization': headers.Authorization,
  268. 'clientid': headers.clientid
  269. },
  270. body: formData
  271. })
  272. const result = await res.json()
  273. if (result.code === 200) {
  274. form.vaccineCert = result.data.ossId
  275. vaccineCertDisplayUrl.value = result.data.url
  276. } else {
  277. ElMessage.error(result.msg || '疫苗凭证上传失败')
  278. }
  279. } catch (e) {
  280. ElMessage.error('疫苗凭证上传失败')
  281. }
  282. }
  283. const saveData = () => {
  284. if (!form.name) return ElMessage.warning('请输入宠物姓名')
  285. if (!form.userId) return ElMessage.warning('请先选择或新增所属主人')
  286. if (!form.breed) return ElMessage.warning('请输入品种');
  287. if (!form.size) return ElMessage.warning('请选择体型');
  288. if (form.weight === undefined || form.weight === null) return ElMessage.warning('请输入体重(kg)');
  289. if (form.age === undefined || form.age === null) return ElMessage.warning('请输入年龄(岁)');
  290. if (!form.houseType) return ElMessage.warning('请选择家庭房屋类型');
  291. if (!form.entryMethod) return ElMessage.warning('请选择入门方式');
  292. if (form.entryMethod === 'password' && !form.entryPassword) return ElMessage.warning('请输入门锁密码');
  293. if (form.entryMethod === 'key' && !form.keyLocation) return ElMessage.warning('请输入钥匙存放位置');
  294. if (!form.healthStatus) return ElMessage.warning('请选择健康状态');
  295. if (form.aggression === undefined || form.aggression === null) return ElMessage.warning('请选择是否有攻击倾向');
  296. if (!form.vaccineStatus) return ElMessage.warning('请选择疫苗情况');
  297. if (!form.medicalHistory) return ElMessage.warning('请输入既往病史');
  298. if (!form.allergies) return ElMessage.warning('请输入过敏史');
  299. submitLoading.value = true
  300. addPetOnOrder(form).then(res => {
  301. emit('success', res.data)
  302. emit('update:visible', false)
  303. }).finally(() => {
  304. submitLoading.value = false
  305. })
  306. }
  307. onMounted(() => {
  308. loadTags()
  309. })
  310. </script>
  311. <style scoped>
  312. .avatar-uploader-icon {
  313. font-size: 28px;
  314. color: #8c939d;
  315. width: 80px;
  316. height: 80px;
  317. text-align: center;
  318. border: 1px dashed #dcdfe6;
  319. border-radius: 50%;
  320. display: flex;
  321. justify-content: center;
  322. align-items: center;
  323. }
  324. .avatar-uploader-icon:hover {
  325. border-color: var(--el-color-primary);
  326. }
  327. </style>