mcSubmit.vue 15 KB

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