demo.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <template>
  2. <div class="demo full">
  3. <h2>{{ translate('basic') }}</h2>
  4. <nut-form>
  5. <nut-form-item :label="translate('name')">
  6. <nut-input v-model="basicData.name" class="nut-input-text" :placeholder="translate('nameTip')" type="text" />
  7. </nut-form-item>
  8. <nut-form-item :label="translate('age')">
  9. <nut-input v-model="basicData.age" class="nut-input-text" :placeholder="translate('ageTip')" type="text" />
  10. </nut-form-item>
  11. <nut-form-item :label="translate('tel')">
  12. <nut-input v-model="basicData.tel" class="nut-input-text" :placeholder="translate('telTip')" type="text" />
  13. </nut-form-item>
  14. <nut-form-item :label="translate('address')">
  15. <nut-input
  16. v-model="basicData.address"
  17. class="nut-input-text"
  18. :placeholder="translate('addressTip')"
  19. type="text"
  20. />
  21. </nut-form-item>
  22. <nut-form-item :label="translate('remarks')">
  23. <nut-textarea :placeholder="translate('remarksTip')" type="text" />
  24. </nut-form-item>
  25. </nut-form>
  26. <h2>{{ translate('title1') }}</h2>
  27. <nut-form :model-value="dynamicForm.state" ref="dynamicRefForm">
  28. <nut-form-item
  29. :label="translate('name')"
  30. prop="name"
  31. required
  32. :rules="[{ required: true, message: translate('nameTip') }]"
  33. >
  34. <nut-input
  35. class="nut-input-text"
  36. v-model="dynamicForm.state.name"
  37. :placeholder="translate('nameTip')"
  38. type="text"
  39. />
  40. </nut-form-item>
  41. <nut-form-item
  42. :label="translate('tel') + index"
  43. :prop="'tels.' + index + '.value'"
  44. required
  45. :rules="[{ required: true, message: translate('telTip') + index }]"
  46. :key="item.key"
  47. v-for="(item, index) in dynamicForm.state.tels"
  48. >
  49. <nut-input class="nut-input-text" v-model="item.value" :placeholder="translate('telTip') + index" type="text" />
  50. </nut-form-item>
  51. <nut-cell>
  52. <nut-button size="small" style="margin-right: 10px" @click="dynamicForm.methods.add"
  53. >{{ translate('add') }}
  54. </nut-button>
  55. <nut-button size="small" style="margin-right: 10px" @click="dynamicForm.methods.remove"
  56. >{{ translate('remove') }}
  57. </nut-button>
  58. <nut-button type="primary" style="margin-right: 10px" size="small" @click="dynamicForm.methods.submit">
  59. {{ translate('submit') }}
  60. </nut-button>
  61. <nut-button size="small" @click="dynamicForm.methods.reset">{{ translate('reset') }}</nut-button>
  62. </nut-cell>
  63. </nut-form>
  64. <h2>{{ translate('title2') }}</h2>
  65. <nut-form
  66. :model-value="formData"
  67. :rules="{
  68. name: [
  69. {
  70. message: '名称两个字以上',
  71. validator: nameLengthValidator
  72. }
  73. ]
  74. }"
  75. ref="ruleForm"
  76. >
  77. <nut-form-item
  78. :label="translate('name')"
  79. prop="name"
  80. required
  81. :rules="[{ required: true, message: translate('nameTip') }]"
  82. >
  83. <nut-input
  84. class="nut-input-text"
  85. @blur="customBlurValidate('name')"
  86. v-model="formData.name"
  87. :placeholder="translate('nameTip1')"
  88. type="text"
  89. />
  90. </nut-form-item>
  91. <nut-form-item
  92. :label="translate('age')"
  93. prop="age"
  94. required
  95. :rules="[
  96. { required: true, message: translate('ageTip') },
  97. { validator: customValidator, message: translate('ageTip2') },
  98. { validator: customRulePropValidator, message: translate('ageTip2'), reg: /^\d+$/ },
  99. { regex: /^(\d{1,2}|1\d{2}|200)$/, message: translate('ageTip3') }
  100. ]"
  101. >
  102. <nut-input class="nut-input-text" v-model="formData.age" :placeholder="translate('ageTip1')" type="text" />
  103. </nut-form-item>
  104. <nut-form-item
  105. :label="translate('tel')"
  106. prop="tel"
  107. required
  108. :rules="[
  109. { required: true, message: translate('telTip') },
  110. { validator: asyncValidator, message: translate('telTip2') }
  111. ]"
  112. >
  113. <nut-input class="nut-input-text" v-model="formData.tel" :placeholder="translate('telTip1')" type="text" />
  114. </nut-form-item>
  115. <nut-form-item
  116. :label="translate('address')"
  117. prop="address"
  118. required
  119. :rules="[{ required: true, message: translate('addressTip') }]"
  120. >
  121. <nut-input
  122. class="nut-input-text"
  123. v-model="formData.address"
  124. :placeholder="translate('addressTip')"
  125. type="text"
  126. />
  127. </nut-form-item>
  128. <nut-cell>
  129. <nut-button type="primary" size="small" style="margin-right: 10px" @click="submit">
  130. {{ translate('submit') }}
  131. </nut-button>
  132. <nut-button size="small" @click="reset"> {{ translate('reset') }}</nut-button>
  133. </nut-cell>
  134. </nut-form>
  135. <h2>{{ translate('title3') }}</h2>
  136. <nut-form>
  137. <nut-form-item :label="translate('switch')">
  138. <nut-switch v-model="formData2.switch"></nut-switch>
  139. </nut-form-item>
  140. <nut-form-item :label="translate('checkbox')">
  141. <nut-checkbox v-model="formData2.checkbox">{{ translate('checkbox') }}</nut-checkbox>
  142. </nut-form-item>
  143. <nut-form-item :label="translate('radiogroup')">
  144. <nut-radio-group direction="horizontal" v-model="formData2.radio">
  145. <nut-radio label="1">{{ translate('option', 1) }}</nut-radio>
  146. <nut-radio disabled label="2">{{ translate('option', 2) }}</nut-radio>
  147. <nut-radio label="3">{{ translate('option', 3) }}</nut-radio>
  148. </nut-radio-group>
  149. </nut-form-item>
  150. <nut-form-item :label="translate('rate')">
  151. <nut-rate v-model="formData2.rate" />
  152. </nut-form-item>
  153. <nut-form-item :label="translate('inputnumber')">
  154. <nut-input-number v-model="formData2.number" />
  155. </nut-form-item>
  156. <nut-form-item :label="translate('range')">
  157. <nut-range hidden-tag v-model="formData2.range"></nut-range>
  158. </nut-form-item>
  159. <nut-form-item :label="translate('uploader')">
  160. <nut-uploader
  161. url="http://apiurl"
  162. accept="image/*"
  163. v-model:file-list="formData2.defaultFileList"
  164. maximum="3"
  165. multiple
  166. >
  167. </nut-uploader>
  168. </nut-form-item>
  169. <nut-form-item :label="translate('address')">
  170. <nut-input
  171. class="nut-input-text"
  172. v-model="formData2.address"
  173. @click="addressModule.methods.show"
  174. readonly
  175. :placeholder="translate('addressTip1')"
  176. type="text"
  177. />
  178. <!-- nut-address -->
  179. <nut-address
  180. v-model:visible="addressModule.state.show"
  181. :province="addressModule.state.province"
  182. :city="addressModule.state.city"
  183. :country="addressModule.state.country"
  184. :town="addressModule.state.town"
  185. @change="addressModule.methods.onChange"
  186. :custom-address-title="translate('addressTip2')"
  187. ></nut-address>
  188. </nut-form-item>
  189. </nut-form>
  190. </div>
  191. </template>
  192. <script lang="ts">
  193. import { Toast } from '@/packages/nutui.vue';
  194. import { reactive, ref } from 'vue';
  195. import { createComponent } from '@/packages/utils/create';
  196. const { createDemo, translate } = createComponent('form');
  197. import { useTranslate } from '@/sites/assets/util/useTranslate';
  198. import { FormItemRuleWithoutValidator } from '../formitem/types';
  199. const initTranslate = () =>
  200. useTranslate({
  201. 'zh-CN': {
  202. basic: '基本用法',
  203. title1: '动态表单',
  204. title2: '表单校验',
  205. title3: '表单类型',
  206. name: '姓名',
  207. nameTip: '请输入姓名',
  208. nameTip1: '请输入姓名,blur 事件校验',
  209. age: '年龄',
  210. ageTip: '请输入年龄',
  211. ageTip1: '请输入年龄,必须数字且0-200区间',
  212. ageTip2: '必须输入数字',
  213. ageTip3: '必须输入0-200区间',
  214. tel: '联系电话',
  215. telTip: '请输入联系电话',
  216. telTip1: '异步校验电话格式',
  217. telTip2: '电话格式不正确',
  218. address: '地址',
  219. addressTip: '请输入地址',
  220. addressTip1: '请选择地址',
  221. addressTip2: '请选择所在地区',
  222. remarks: '备注',
  223. remarksTip: '请输入备注',
  224. add: '添加',
  225. remove: '删除',
  226. submit: '提交',
  227. reset: '重置提示状态',
  228. switch: '开关',
  229. checkbox: '复选框',
  230. radiogroup: '单选按钮',
  231. option: (v: string) => '选项' + v,
  232. rate: '评分',
  233. inputnumber: '步进器',
  234. range: '滑块',
  235. uploader: '文件上传',
  236. success: '上传成功',
  237. uploading: '上传中...',
  238. asyncValidator: '模拟异步验证中'
  239. },
  240. 'en-US': {
  241. basic: 'Basic Usage',
  242. title1: 'Dynamic Form',
  243. title2: 'Validate Form',
  244. title3: 'Form Type',
  245. name: 'Name',
  246. nameTip: 'Please enter your name',
  247. nameTip1: 'Please enter , blur event validate',
  248. age: 'Age',
  249. ageTip: 'Please enter age',
  250. ageTip1: 'Please enter the age, which must be numeric and in the range of 0-200',
  251. ageTip2: 'You must enter a number',
  252. ageTip3: 'The range 0-200 must be entered',
  253. tel: 'Tel',
  254. telTip: 'Please enter tel',
  255. telTip1: 'Async check tel format',
  256. telTip2: 'Tel format is incorrect',
  257. address: 'Address',
  258. addressTip: 'Please enter address',
  259. addressTip1: 'Please select an address',
  260. addressTip2: 'Please select your region',
  261. remarks: 'Remarks',
  262. remarksTip: 'Please enter remarks',
  263. add: 'Add',
  264. remove: 'Remove',
  265. submit: 'Submit',
  266. reset: 'Reset prompt status',
  267. switch: 'Switch',
  268. checkbox: 'Checkbox',
  269. radiogroup: 'Radiogroup',
  270. option: (v: string) => 'Option' + v,
  271. rate: 'Rate',
  272. inputnumber: 'Inputnumber',
  273. range: 'Range',
  274. uploader: 'Upload file',
  275. success: 'Upload successful',
  276. uploading: 'Uploading',
  277. asyncValidator: 'Simulating asynchronous verification'
  278. }
  279. });
  280. export default createDemo({
  281. props: {},
  282. setup() {
  283. initTranslate();
  284. const formData = reactive({
  285. name: '',
  286. age: '',
  287. tel: '',
  288. address: ''
  289. });
  290. const basicData = reactive({
  291. name: '',
  292. age: '',
  293. tel: '',
  294. address: ''
  295. });
  296. const dynamicRefForm = ref<any>(null);
  297. const dynamicForm = {
  298. state: reactive({
  299. name: '',
  300. tels: new Array({
  301. key: 1,
  302. value: ''
  303. })
  304. }),
  305. methods: {
  306. submit() {
  307. dynamicRefForm.value.validate().then(({ valid, errors }: any) => {
  308. if (valid) {
  309. console.log('success', dynamicForm);
  310. } else {
  311. Toast.warn(errors[0].message);
  312. console.log('error submit!!', errors);
  313. }
  314. });
  315. },
  316. reset() {
  317. dynamicRefForm.value.reset();
  318. },
  319. remove() {
  320. dynamicForm.state.tels.splice(dynamicForm.state.tels.length - 1, 1);
  321. },
  322. add() {
  323. let newIndex = dynamicForm.state.tels.length;
  324. dynamicForm.state.tels.push({
  325. key: Date.now(),
  326. value: ''
  327. });
  328. }
  329. }
  330. };
  331. const validate = (item: any) => {
  332. console.log(item);
  333. };
  334. const formData2 = reactive({
  335. switch: false,
  336. checkbox: false,
  337. radio: 0,
  338. number: 0,
  339. rate: 3,
  340. range: 30,
  341. address: '',
  342. defaultFileList: [
  343. {
  344. name: 'file 1.png',
  345. url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
  346. status: 'success',
  347. message: translate('success'),
  348. type: 'image'
  349. },
  350. {
  351. name: 'file 2.png',
  352. url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
  353. status: 'uploading',
  354. message: translate('uploading'),
  355. type: 'image'
  356. }
  357. ]
  358. });
  359. const addressModule = reactive({
  360. state: {
  361. show: false,
  362. province: [
  363. { id: 1, name: '北京' },
  364. { id: 2, name: '广西' },
  365. { id: 3, name: '江西' },
  366. { id: 4, name: '四川' }
  367. ],
  368. city: [
  369. { id: 7, name: '朝阳区' },
  370. { id: 8, name: '崇文区' },
  371. { id: 9, name: '昌平区' },
  372. { id: 6, name: '石景山区' }
  373. ],
  374. country: [
  375. { id: 3, name: '八里庄街道' },
  376. { id: 9, name: '北苑' },
  377. { id: 4, name: '常营乡' }
  378. ],
  379. town: []
  380. },
  381. methods: {
  382. show() {
  383. addressModule.state.show = !addressModule.state.show;
  384. if (addressModule.state.show) {
  385. formData2.address = '';
  386. }
  387. },
  388. onChange({ custom, next, value }: any) {
  389. formData2.address += value.name;
  390. const name = addressModule.state[next];
  391. if (name.length < 1) {
  392. addressModule.state.show = false;
  393. }
  394. }
  395. }
  396. });
  397. const ruleForm = ref<any>(null);
  398. const submit = () => {
  399. ruleForm.value.validate().then(({ valid, errors }: any) => {
  400. if (valid) {
  401. console.log('success', formData);
  402. } else {
  403. console.log('error submit!!', errors);
  404. }
  405. });
  406. };
  407. const reset = () => {
  408. ruleForm.value.reset();
  409. };
  410. // 失去焦点校验
  411. const customBlurValidate = (prop: string) => {
  412. ruleForm.value.validate(prop).then(({ valid, errors }: any) => {
  413. if (valid) {
  414. console.log('success', formData);
  415. } else {
  416. console.log('error submit!!', errors);
  417. }
  418. });
  419. };
  420. // 函数校验
  421. const customValidator = (val: string) => /^\d+$/.test(val);
  422. const customRulePropValidator = (val: string, rule: FormItemRuleWithoutValidator) => {
  423. return (rule?.reg as RegExp).test(val);
  424. };
  425. const nameLengthValidator = (val: string) => val?.length >= 2;
  426. // Promise 异步校验
  427. const asyncValidator = (val: string) => {
  428. return new Promise((resolve) => {
  429. Toast.loading(translate('asyncValidator'));
  430. setTimeout(() => {
  431. Toast.hide();
  432. resolve(/^400(-?)[0-9]{7}$|^1\d{10}$|^0[0-9]{2,3}-[0-9]{7,8}$/.test(val));
  433. }, 1000);
  434. });
  435. };
  436. return {
  437. ruleForm,
  438. formData,
  439. validate,
  440. customValidator,
  441. customRulePropValidator,
  442. nameLengthValidator,
  443. asyncValidator,
  444. customBlurValidate,
  445. submit,
  446. reset,
  447. formData2,
  448. addressModule,
  449. dynamicForm,
  450. dynamicRefForm,
  451. basicData,
  452. translate
  453. };
  454. }
  455. });
  456. </script>
  457. <style lang="scss" scoped></style>