pcSubmit.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <template>
  2. <div class="pcSubmit">
  3. <a-form layout="vertical" :model="formData" ref="formRef" :rules="rules">
  4. <a-form-item name="problemDesc">
  5. <div class="myTitle required" slot="label"><span class="number">01</span>{{ t('feedback.title1') }}</div>
  6. <a-textarea
  7. show-count
  8. :maxlength="500"
  9. :autoSize="{ minRows: 4, maxRows: 8 }"
  10. v-model:value="formData.problemDesc"
  11. :placeholder="t('feedback.settext')"
  12. />
  13. </a-form-item>
  14. <a-form-item name="problemFile" style="margin-top: 2px">
  15. <a-upload
  16. v-model:file-list="formData.problemDescImgs"
  17. accept=".jpg,.png,.mp4"
  18. action="/service/manage/common/upload/files"
  19. list-type="picture-card"
  20. :maxCount="6"
  21. :multiple="true"
  22. :before-upload="beforeUpload"
  23. @preview="handlePreview"
  24. @change="fileChange"
  25. >
  26. <div>
  27. <PlusOutlined style="color: #d9d9d9" color="#D9D9D9" />
  28. <!-- <div style="margin-top: 8px">{{ t('feedback.upload') }}</div> -->
  29. </div>
  30. </a-upload>
  31. <div class="tips" v-html="t('feedback.fileTipsPc')"></div>
  32. </a-form-item>
  33. <a-form-item name="solution">
  34. <div class="myTitle required" slot="label"><span class="number">02</span>{{ t('feedback.title2') }}</div>
  35. <a-textarea
  36. show-count
  37. :maxlength="500"
  38. :autoSize="{ minRows: 4, maxRows: 8 }"
  39. v-model:value="formData.solution"
  40. :placeholder="t('feedback.settext')"
  41. />
  42. </a-form-item>
  43. <a-form-item name="problemFile" style="margin-top: 2px">
  44. <a-upload
  45. :maxCount="6"
  46. v-model:file-list="formData.solutionImgs"
  47. action="/service/manage/common/upload/files"
  48. list-type="picture-card"
  49. :multiple="true"
  50. :before-upload="beforeUpload"
  51. @preview="handlePreview"
  52. @change="fileChange"
  53. >
  54. <div>
  55. <PlusOutlined style="color: #d9d9d9" color="#D9D9D9" />
  56. <!-- <div style="margin-top: 8px">{{ t('feedback.upload') }}</div> -->
  57. </div>
  58. </a-upload>
  59. <div class="tips" v-html="t('feedback.fileTipsPc')"></div>
  60. </a-form-item>
  61. <a-form-item name="industryOptionId">
  62. <div class="myTitle required" slot="label"><span class="number">03</span>{{ t('feedback.title3') }}</div>
  63. <a-select
  64. key="industryOptionId"
  65. v-model:value="formData.industryOptionId"
  66. :getPopupContainer="triggerNode => {return triggerNode.parentNode;}"
  67. :placeholder="t('feedback.setselcet')"
  68. :options="propsOptions.industryOptionId"
  69. />
  70. </a-form-item>
  71. <a-form-item name="hardwareOptionId">
  72. <div class="myTitle required" slot="label"><span class="number">04</span>{{ t('feedback.title4') }}</div>
  73. <a-select
  74. key="hardwareOptionId"
  75. v-model:value="formData.hardwareOptionId"
  76. :placeholder="t('feedback.setselcet')"
  77. :getPopupContainer="triggerNode => {return triggerNode.parentNode;}"
  78. :options="propsOptions.hardwareOptionId"
  79. />
  80. </a-form-item>
  81. <a-form-item name="softwareOptionId">
  82. <div class="myTitle required" slot="label"><span class="number">05</span>{{ t('feedback.title5') }}</div>
  83. <a-select
  84. key="softwareOptionId"
  85. v-model:value="formData.softwareOptionId"
  86. :placeholder="t('feedback.setselcet')"
  87. :getPopupContainer="triggerNode => {return triggerNode.parentNode;}"
  88. :options="propsOptions.softwareOptionId"
  89. />
  90. </a-form-item>
  91. <a-form-item>
  92. <div class="myTitle" slot="label"><span class="number">06</span>{{ t('feedback.title6') }}</div>
  93. <a-input :maxlength="30" v-model:value="formData.nickName" :placeholder="t('feedback.settext')" />
  94. </a-form-item>
  95. <a-form-item>
  96. <div class="myTitle" slot="label"><span class="number">07</span>{{ t('feedback.title61') }}</div>
  97. <a-input :maxlength="30" v-model:value="formData.phone" :placeholder="t('feedback.settext')" />
  98. </a-form-item>
  99. <a-form-item>
  100. <div class="myTitle" slot="label"><span class="number">08</span>{{ t('feedback.title7') }}</div>
  101. <a-select
  102. v-model:value="formData.country"
  103. :filterOption="filterOption"
  104. :options="countryOption"
  105. :getPopupContainer="triggerNode => {return triggerNode.parentNode;}"
  106. showSearch
  107. :placeholder="t('feedback.setselcet')"
  108. />
  109. <!-- <a-cascader v-model:value="formData.countries" :options="options" placeholder="Please select" /> -->
  110. </a-form-item>
  111. <a-form-item v-if="formData.country == '中国' || formData.country == 'China'">
  112. <div class="myTitle" slot="label"
  113. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '09' : '08' }}</span
  114. >{{ t('feedback.title71') }}</div
  115. >
  116. <a-cascader v-model:value="formData.countries" :options="options" :placeholder="t('feedback.setselcet')" />
  117. </a-form-item>
  118. <a-form-item>
  119. <div class="myTitle" slot="label"
  120. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '10' : '09' }}</span
  121. >{{ t('feedback.title8') }}</div
  122. >
  123. <van-rate
  124. color="#FADB14"
  125. size="30"
  126. void-color="#D9D9D9"
  127. :allow-half="true"
  128. v-model="formData.score"
  129. :placeholder="t('feedback.setselcet')"
  130. />
  131. </a-form-item>
  132. <a-form-item>
  133. <div class="myTitle" slot="label"
  134. ><span class="number">{{ formData.country == '中国' || formData.country == 'China' ? '11' : '10' }}</span
  135. >{{ t('feedback.title9') }}</div
  136. >
  137. <a-input :maxlength="100" v-model:value="formData.scoreReason" :placeholder="t('feedback.settext')" />
  138. </a-form-item>
  139. <a-form-item style="text-align: center">
  140. <a-button class="w-160px" style="background-color: #00b3ec" type="primary" size="large" @click="onSubmit">{{
  141. t('feedback.Submit')
  142. }}</a-button>
  143. </a-form-item>
  144. </a-form>
  145. </div>
  146. </template>
  147. <script setup>
  148. import { ref, watch } from 'vue';
  149. import { PlusOutlined } from '@ant-design/icons-vue';
  150. import cityList from './area.json';
  151. import countryList from './country.json';
  152. // import { createImgPreview } from '/@/components/Preview/index';
  153. const props = defineProps(['formData', 'columns', 'addres']);
  154. import { message, Upload } from 'ant-design-vue';
  155. import { useI18n } from 'vue-i18n';
  156. const { t, locale } = useI18n();
  157. const emit = defineEmits(['submit']);
  158. watch(
  159. () => props.addres,
  160. (newValue, oldValue) => {
  161. console.log('addreswatch', newValue);
  162. formData.value.country = newValue.country;
  163. formData.value.countries = newValue.mccountries;
  164. },
  165. );
  166. const formData = ref({
  167. problemDesc: '',
  168. problemDescImgs: [],
  169. hardwareOptionId: null,
  170. softwareOptionId: null,
  171. industryOptionId: null,
  172. solution: '',
  173. solutionImgs: [],
  174. nickName: '',
  175. phone: '',
  176. score: 0,
  177. scoreReason: '',
  178. country: '',
  179. address: '',
  180. countries: [],
  181. });
  182. console.log('formData', formData);
  183. // eslint-disable-next-line vue/no-setup-props-destructure
  184. const propsOptions = props.columns;
  185. const previewImage = ref('');
  186. const formRef = ref(null);
  187. const rules = {
  188. problemDesc: [
  189. { name: 'problemDesc', validator: validatorFile, message: t('feedback.settext') + t('feedback.title1'), trigger: 'change' },
  190. ],
  191. solution: [{ name: 'solution', validator: validatorFile, message: t('feedback.settext') + t('feedback.title2'), trigger: 'change' }],
  192. softwareOptionId: [{ required: true, message: t('feedback.setselcet') + t('feedback.title5'), trigger: 'change' }],
  193. industryOptionId: [{ required: true, message: t('feedback.setselcet') + t('feedback.title3'), trigger: 'change' }],
  194. hardwareOptionId: [{ required: true, message: t('feedback.setselcet') + t('feedback.title4'), trigger: 'change' }],
  195. };
  196. function validatorFile(_rule, value) {
  197. let rule = _rule;
  198. let isOk = Boolean(formData.value[rule.name] || (formData.value[`${rule.name}Imgs`] && formData.value[`${rule.name}Imgs`].length > 0));
  199. if (isOk) {
  200. return Promise.resolve();
  201. } else {
  202. return Promise.reject(rule.message);
  203. }
  204. }
  205. function fileChange() {
  206. formRef.value.validateFields(['problemDesc', 'solution']);
  207. }
  208. const countryOption = countryList.map((ele) => {
  209. return {
  210. value: ele.chinese,
  211. label: locale.value == 'en-us' ? ele.english : ele.chinese,
  212. english: locale.value == 'en-us' ? ele.chinese : ele.english,
  213. };
  214. });
  215. function filterOption(inputValue, option) {
  216. return (
  217. option.label.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0 ||
  218. option.english.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0
  219. );
  220. }
  221. const options = cityList.map((ele) => {
  222. return {
  223. value: ele.name,
  224. label: ele.name,
  225. children: ele.city.map((element) => {
  226. return {
  227. value: element.name,
  228. label: element.name,
  229. };
  230. }),
  231. };
  232. });
  233. const beforeUpload = (file) => {
  234. console.log('beforeUpload', file);
  235. const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'video/mp4';
  236. if (!isJpgOrPng) {
  237. message.error(t('feedback.fileTips'));
  238. return false;
  239. }
  240. const isLt2M = file.size / 1024 / 1024 < 5;
  241. const isLt50M = file.size / 1024 / 1024 < 50;
  242. if (!isLt2M && (file.type === 'image/jpeg' || file.type === 'image/png')) {
  243. message.error(t('feedback.fileTips'));
  244. return false;
  245. }
  246. if (!isLt50M && file.type === 'video/mp4') {
  247. message.error(t('feedback.fileTips'));
  248. return Upload.LIST_IGNORE;
  249. }
  250. return file.type === 'video/mp4' ? isLt50M && isJpgOrPng : isJpgOrPng && isLt2M;
  251. };
  252. // const previewFile = (file) => {
  253. // console.log('file',file)
  254. // return file
  255. // }
  256. function getBase64(file) {
  257. return new Promise((resolve, reject) => {
  258. const reader = new FileReader();
  259. reader.readAsDataURL(file);
  260. reader.onload = () => resolve(reader.result);
  261. reader.onerror = (error) => reject(error);
  262. });
  263. }
  264. const handlePreview = async (file) => {
  265. console.log('file', file);
  266. file.preview = file.response.data;
  267. window.open(file.response.data);
  268. return;
  269. // createImgPreview({ imageList: [file.response.data] });
  270. };
  271. const preview = (file) => {
  272. console.log('file', file);
  273. return file;
  274. };
  275. const onSubmit = () => {
  276. console.log('values', formRef.value.validate());
  277. formRef.value
  278. .validate()
  279. .then(() => {
  280. emit('submit', formData.value);
  281. console.log('values', formData);
  282. })
  283. .catch((error) => {
  284. console.log('error', error);
  285. });
  286. };
  287. </script>
  288. <style lang="scss" scoped>
  289. .myTitle {
  290. position: relative;
  291. margin-bottom: 14px;
  292. margin-top: 48px;
  293. .number {
  294. font-size: 16px;
  295. font-family: Microsoft YaHei, Microsoft YaHei;
  296. font-weight: bold;
  297. color: #00b3ec;
  298. }
  299. span {
  300. font-size: 16px;
  301. font-family: Microsoft YaHei, Microsoft YaHei;
  302. font-weight: 400;
  303. color: #333333;
  304. line-height: 19px;
  305. margin-right: 7px;
  306. }
  307. }
  308. .tips {
  309. font-size: 14px;
  310. font-family: Microsoft YaHei, Microsoft YaHei;
  311. font-weight: 400;
  312. color: #cccccc;
  313. line-height: 16px;
  314. margin-top: 8px;
  315. }
  316. .required {
  317. &::before {
  318. display: inline-block;
  319. margin-inline-end: 4px;
  320. color: #ff4d4f;
  321. font-size: 14px;
  322. font-family: SimSun, sans-serif;
  323. line-height: 1;
  324. content: '*';
  325. position: absolute;
  326. left: -11px;
  327. top: 4.5px;
  328. }
  329. }
  330. </style>