mcSubmit.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <template>
  2. <div class="mcSubmit">
  3. <van-form @submit="onSubmit" ref="submits" style="background: #fff">
  4. <van-cell-group style="margin: none" inset>
  5. <div class="myTitle required"><span class="number">01</span>{{ t('feedback.title1') }}</div>
  6. <van-field
  7. class="zdycell"
  8. v-model="formData.problemDesc"
  9. label-align="top"
  10. name="故障描述"
  11. label=""
  12. rows="2"
  13. autoSize
  14. :max-count="5"
  15. type="textarea"
  16. maxlength="500"
  17. show-word-limit
  18. :rules="[
  19. { name: 'problemDesc', validator: validator, trigger: 'onChange', message: t('feedback.settext') + t('feedback.title1') },
  20. ]"
  21. :placeholder="t('feedback.settext')"
  22. />
  23. <van-field class="uploadercell" name="uploader" label-align="top">
  24. <template #input>
  25. <div>
  26. <div>
  27. <van-uploader
  28. upload-icon="plus"
  29. :before-read="beforeRead"
  30. :after-read="clzpAfterRead"
  31. accept=".jpg,.png,.mp4"
  32. :max-count="6"
  33. v-model="formData.problemDescImgs"
  34. />
  35. </div>
  36. <div class="tips" v-html="t('feedback.fileTipsPc')"></div>
  37. </div>
  38. </template>
  39. </van-field>
  40. <div class="myTitle required"><span class="number">02</span>{{ t('feedback.title2') }}</div>
  41. <van-field
  42. v-model="formData.solution"
  43. label-align="top"
  44. class="zdycell"
  45. name="故障描述"
  46. label=""
  47. rows="2"
  48. autoSize
  49. type="textarea"
  50. maxlength="500"
  51. show-word-limit
  52. :rules="[{ name: 'solution', validator: validator, trigger: 'onChange', message: t('feedback.settext') + t('feedback.title2') }]"
  53. :placeholder="t('feedback.settext')"
  54. />
  55. <van-field class="uploadercell" name="uploader" label-align="top">
  56. <template #input>
  57. <div>
  58. <div>
  59. <van-uploader
  60. upload-icon="plus"
  61. class="uploadercell"
  62. :before-read="beforeRead"
  63. :after-read="clzpAfterRead"
  64. :max-count="6"
  65. v-model="formData.solutionImgs"
  66. />
  67. </div>
  68. <div class="tips" v-html="t('feedback.fileTipsPc')"></div>
  69. </div>
  70. </template>
  71. </van-field>
  72. <div class="myTitle required"><span class="number">03</span>{{ t('feedback.title3') }}</div>
  73. <van-field
  74. v-model="setObjId.industryOptionId"
  75. class="zdycell"
  76. is-link
  77. readonly
  78. name="picker"
  79. label=""
  80. :placeholder="t('feedback.setselcet')"
  81. :rules="[{ required: true, message: t('feedback.setselcet') + t('feedback.title3') }]"
  82. @click="showPicker.industryOptionId = true"
  83. />
  84. <van-popup v-model:show="showPicker.industryOptionId" position="bottom">
  85. <van-picker
  86. :columns="propsOptions.industryOptionId"
  87. @confirm="(val) => onConfirm(val, 'industryOptionId')"
  88. @cancel="showPicker.industryOptionId = false"
  89. />
  90. </van-popup>
  91. <div class="myTitle required"><span class="number">04</span>{{ t('feedback.title4') }}</div>
  92. <van-field
  93. class="zdycell"
  94. v-model="setObjId.hardwareOptionId"
  95. is-link
  96. readonly
  97. name="picker"
  98. label=""
  99. :placeholder="t('feedback.setselcet')"
  100. :rules="[{ required: true, message: t('feedback.setselcet') + t('feedback.title4') }]"
  101. @click="showPicker.hardwareOptionId = true"
  102. />
  103. <van-popup v-model:show="showPicker.hardwareOptionId" position="bottom">
  104. <van-picker
  105. :columns="propsOptions.hardwareOptionId"
  106. @confirm="(val) => onConfirm(val, 'hardwareOptionId')"
  107. @cancel="showPicker.hardwareOptionId = false"
  108. />
  109. </van-popup>
  110. <div class="myTitle required"><span class="number">05</span>{{ t('feedback.title5') }}</div>
  111. <van-field
  112. class="zdycell"
  113. v-model="setObjId.softwareOptionId"
  114. is-link
  115. readonly
  116. name="picker"
  117. label=""
  118. maxlength="30"
  119. :placeholder="t('feedback.setselcet')"
  120. :rules="[{ required: true, message: t('feedback.setselcet') + t('feedback.title5') }]"
  121. @click="showPicker.softwareOptionId = true"
  122. />
  123. <van-popup v-model:show="showPicker.softwareOptionId" position="bottom">
  124. <van-picker
  125. :columns="propsOptions.softwareOptionId"
  126. @confirm="(val) => onConfirm(val, 'softwareOptionId')"
  127. @cancel="showPicker.softwareOptionId = false"
  128. />
  129. </van-popup>
  130. <div class="myTitle"><span class="number">06</span>{{ t('feedback.title6') }}</div>
  131. <van-field
  132. class="zdycell"
  133. v-model="formData.nickName"
  134. label-align="top"
  135. name="联系电话"
  136. maxlength="15"
  137. label=""
  138. :placeholder="t('feedback.settext')"
  139. />
  140. <div class="myTitle"><span class="number">07</span>{{ t('feedback.title61') }}</div>
  141. <van-field
  142. class="zdycell"
  143. v-model="formData.phone"
  144. label-align="top"
  145. name="联系电话"
  146. maxlength="11"
  147. label=""
  148. :placeholder="t('feedback.settext')"
  149. />
  150. <div class="myTitle"><span class="number">08</span>{{ t('feedback.title7') }}</div>
  151. <van-field
  152. v-model="formData.country"
  153. is-link
  154. class="zdycell"
  155. readonly
  156. name="picker"
  157. label=""
  158. :placeholder="t('feedback.setselcet')"
  159. @click="showPicker.country = true"
  160. />
  161. <van-popup v-model:show="showPicker.country" position="bottom">
  162. <van-picker :columns="columnsCountry" @confirm="(val) => onConfirm(val, 'country')" @cancel="showPicker.country = false" />
  163. </van-popup>
  164. <div class="myTitle" v-if="formData.country == '中国' || formData.country == 'China'"
  165. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '09' : '08' }}</span
  166. >{{ t('feedback.title71') }}</div
  167. >
  168. <van-field
  169. v-if="formData.country == '中国' || formData.country == 'China'"
  170. v-model="formData.city"
  171. class="zdycell"
  172. is-link
  173. readonly
  174. name="area"
  175. label=""
  176. :placeholder="t('feedback.setselcet')"
  177. @click="showPicker.city = true"
  178. />
  179. <van-popup v-model:show="showPicker.city" position="bottom">
  180. <van-cascader
  181. v-model="cascaderValue"
  182. title="请选择所在地区"
  183. :options="columnsCity"
  184. @close="showPicker.city = false"
  185. @finish="onFinish"
  186. />
  187. </van-popup>
  188. <div class="myTitle"
  189. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '10' : '09' }}</span
  190. >{{ t('feedback.title8') }}</div
  191. >
  192. <van-field class="ratecell" name="rate" label="">
  193. <template #input>
  194. <van-rate size="30" color="#FADB14" void-color="#D9D9D9" :allow-half="true" v-model="formData.score" />
  195. </template>
  196. </van-field>
  197. <div class="myTitle"
  198. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '11' : '10' }}</span
  199. >{{ t('feedback.title9') }}</div
  200. >
  201. <van-field
  202. v-model="formData.scoreReason"
  203. class="zdycell"
  204. label-align="top"
  205. name="联系电话"
  206. maxlength="100"
  207. label=""
  208. :placeholder="t('feedback.settext')"
  209. />
  210. <div style="margin: 54px 0 24px 0; background-color: #f5f5f5">
  211. <van-button style="height: 44px" block color="#00B3EC" type="primary" native-type="submit"> 提交 </van-button>
  212. </div>
  213. </van-cell-group>
  214. </van-form>
  215. </div>
  216. </template>
  217. <script setup>
  218. import { ref, watch } from 'vue';
  219. import cityList from './area.json';
  220. import countryList from './country.json';
  221. import axios from 'axios';
  222. const areaList = ref({});
  223. const props = defineProps(['columns', 'addres']);
  224. const emit = defineEmits(['submit']);
  225. import { showToast } from 'vant';
  226. const propsOptions = props.columns;
  227. const loading = ref(false);
  228. const submits = ref(null);
  229. const setObjId = ref({
  230. hardwareOptionId: null,
  231. softwareOptionId: null,
  232. industryOptionId: null,
  233. });
  234. const cascaderValue = ref();
  235. watch(
  236. () => props.addres,
  237. (newValue, oldValue) => {
  238. console.log('addreswatch', newValue);
  239. formData.value.country = newValue.country;
  240. formData.value.city = newValue.mccountries;
  241. cascaderValue.value = newValue.city;
  242. // 因为watch被观察的对象只能是getter/effect函数、ref、active对象或者这些类型是数组
  243. // 所以需要将state.count变成getter函数
  244. },
  245. );
  246. const formData = ref({
  247. problemDesc: '',
  248. problemDescImgs: [],
  249. hardwareOptionId: null,
  250. softwareOptionId: null,
  251. industryOptionId: null,
  252. solution: '',
  253. solutionImgs: [],
  254. nickName: '',
  255. phone: '',
  256. address: '',
  257. score: 0,
  258. scoreReason: '',
  259. });
  260. import { useI18n } from 'vue-i18n';
  261. const { t, locale } = useI18n();
  262. const showPicker = ref({
  263. country: false,
  264. hardwareOptionId: false,
  265. softwareOptionId: false,
  266. city: false,
  267. });
  268. const columnsCountry = countryList.map((ele) => {
  269. return { text: ele.chinese, value: locale.value == 'en-us' ? ele.english : ele.chinese };
  270. });
  271. const columnsCity = cityList.map((ele) => {
  272. return {
  273. text: ele.name,
  274. value: ele.name,
  275. children: ele.city.map((element) => {
  276. return {
  277. text: element.name,
  278. value: element.name,
  279. };
  280. }),
  281. };
  282. });
  283. // 全部选项选择完毕后,会触发 finish 事件
  284. const onFinish = ({ selectedOptions }, b) => {
  285. showPicker.value.city = false;
  286. console.log('onFinish', cascaderValue.value);
  287. formData.value.city = selectedOptions.map((option) => option.text).join('/');
  288. };
  289. const onConfirm = ({ selectedOptions, selectedValues }, b) => {
  290. formData.value[b] = selectedValues.join(',');
  291. setObjId.value[b] = selectedOptions[0].text;
  292. showPicker.value[b] = false;
  293. };
  294. const onSubmit = () => {
  295. emit('submit', formData.value, setObjId.value);
  296. };
  297. const beforeUpload = (file) => {
  298. console.log('beforeUpload', file);
  299. const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
  300. if (!isJpgOrPng) {
  301. showToast('You can only upload JPG file!');
  302. }
  303. const isLt2M = file.size / 1024 / 1024 < 2;
  304. if (!isLt2M) {
  305. showToast('Image must smaller than 2MB!');
  306. }
  307. return isJpgOrPng && isLt2M;
  308. }; //校验图片的格式
  309. function beforeRead(file) {
  310. const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'video/mp4' || file.type === 'video/quicktime';
  311. if (!isJpgOrPng) {
  312. showToast(t('feedback.fileTips'));
  313. return false;
  314. }
  315. const isLt2M = file.size / 1024 / 1024 < 2;
  316. const isLt50M = file.size / 1024 / 1024 < 50;
  317. if (!isLt2M && (file.type === 'image/jpeg' || file.type === 'image/png')) {
  318. showToast(t('feedback.fileTips'));
  319. return false;
  320. }
  321. if (!isLt50M && (file.type === 'video/mp4' || file.type === 'video/quicktime')) {
  322. showToast(t('feedback.fileTips'));
  323. return false;
  324. }
  325. // if (!/(jpg|jpeg|png|JPG|PNG|mp4|quicktime)/i.test(file.type)) {
  326. // showToast(t('feedback.fileTips'));
  327. // return false;
  328. // }
  329. return true;
  330. }
  331. function validator(value, rule) {
  332. let isOk = Boolean(formData.value[rule.name] || (formData.value[`${rule.name}Imgs`] && formData.value[`${rule.name}Imgs`].length > 0));
  333. return isOk;
  334. }
  335. //照片上传事件方法
  336. function clzpAfterRead(file) {
  337. // 上传状态提示开启
  338. file.status = 'uploading';
  339. file.message = '上传中...';
  340. loading.value = true;
  341. // 创建一个空对象实例
  342. let formData = new FormData();
  343. // 调用append()方法添加数据
  344. formData.append('file', file.file);
  345. axios({
  346. url: '/service/sale/upload/file',
  347. method: 'POST',
  348. data: formData,
  349. headers: {
  350. 'Content-Type': 'multipart/form-data',
  351. },
  352. })
  353. .then((res) => {
  354. loading.value = false;
  355. let { data } = res;
  356. if (data.code == 200) {
  357. // 上传状态提示关闭
  358. file.url = data.data;
  359. file.file = file.file;
  360. file.content = '';
  361. file.status = 'done';
  362. showToast('上传成功!');
  363. setTimeout(() => {
  364. console.log('problemDesc', submits.value);
  365. // submits.value.validate('problemDesc')();
  366. // submits.value.validate('solution');
  367. }, 1000);
  368. }
  369. console.log('formData.faultImg', formData, file);
  370. })
  371. .catch(() => {
  372. loading.value = false;
  373. });
  374. }
  375. </script>
  376. <style lang="scss" scoped>
  377. .mcSubmit {
  378. padding-top: 16px;
  379. .tips {
  380. font-size: 14px;
  381. font-family: Source Han Sans CN, Source Han Sans CN;
  382. font-weight: 400;
  383. color: #cccccc;
  384. line-height: 16px;
  385. margin-top: 15px;
  386. }
  387. .required {
  388. &::before {
  389. display: inline-block;
  390. margin-inline-end: 4px;
  391. color: #ff4d4f;
  392. font-size: 14px;
  393. font-family: SimSun, sans-serif;
  394. line-height: 1;
  395. content: '*';
  396. position: absolute;
  397. left: -11px;
  398. top: 4.5px;
  399. }
  400. }
  401. .myTitle {
  402. position: relative;
  403. margin: 48px 0 15px 0;
  404. // padding: 0 var(--van-cell-horizontal-padding);
  405. .number {
  406. font-size: 16px;
  407. font-family: Microsoft YaHei, Microsoft YaHei;
  408. font-weight: bold;
  409. color: #00b3ec;
  410. }
  411. span {
  412. font-size: 16px;
  413. font-family: Microsoft YaHei, Microsoft YaHei;
  414. font-weight: 400;
  415. color: #333333;
  416. line-height: 19px;
  417. margin-right: 7px;
  418. }
  419. }
  420. ::v-deep .uploadercell:after {
  421. display: none;
  422. }
  423. ::v-deep .ratecell:after {
  424. display: none;
  425. }
  426. ::v-deep .van-cell {
  427. padding: 4px 0px;
  428. }
  429. // ::v-deep .van-uploader__upload{
  430. // background-color: #fff;
  431. // border: 1px solid #e4e4e5;
  432. // border-radius: 5px;
  433. // }
  434. // .zdycell {
  435. // border: 1px solid #e4e4e5;
  436. // border-radius: 5px;
  437. // }
  438. .uploadercell {
  439. padding: 20px 0 0 0;
  440. }
  441. }
  442. </style>