form.uvue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <template>
  2. <cl-page>
  3. <view class="p-3">
  4. <demo-item>
  5. <cl-form
  6. :pt="{
  7. className: 'p-2 pb-0'
  8. }"
  9. v-model="formData"
  10. ref="formRef"
  11. :rules="rules"
  12. :disabled="saving"
  13. label-position="top"
  14. >
  15. <cl-form-item prop="avatarUrl">
  16. <cl-upload v-model="formData.avatarUrl" test></cl-upload>
  17. </cl-form-item>
  18. <cl-form-item :label="t('用户名')" prop="nickName" required>
  19. <cl-input
  20. v-model="formData.nickName"
  21. :placeholder="t('请输入用户名')"
  22. clearable
  23. ></cl-input>
  24. </cl-form-item>
  25. <cl-form-item :label="t('邮箱')" prop="email">
  26. <cl-input
  27. v-model="formData.email"
  28. :placeholder="t('请输入邮箱地址')"
  29. ></cl-input>
  30. </cl-form-item>
  31. <cl-form-item :label="t('动态验证')" required prop="contacts">
  32. <view
  33. class="contacts border border-solid border-surface-200 rounded-xl p-3 dark:!border-surface-700"
  34. >
  35. <cl-form-item
  36. v-for="(item, index) in formData.contacts"
  37. :key="index"
  38. :label="t('联系人') + ` - ${index + 1}`"
  39. :prop="`contacts[${index}].phone`"
  40. :rules="
  41. [
  42. {
  43. required: true,
  44. message: t('手机号不能为空')
  45. }
  46. ] as ClFormRule[]
  47. "
  48. required
  49. >
  50. <view class="flex flex-row items-center">
  51. <cl-input
  52. :pt="{
  53. className: 'flex-1 mr-2'
  54. }"
  55. v-model="item.phone"
  56. :placeholder="t('请输入手机号')"
  57. ></cl-input>
  58. <cl-button
  59. type="light"
  60. icon="subtract-line"
  61. @tap="removeContact(index)"
  62. ></cl-button>
  63. </view>
  64. </cl-form-item>
  65. <cl-button size="large" icon="add-line" @tap="addContact">{{
  66. t("添加联系人")
  67. }}</cl-button>
  68. </view>
  69. </cl-form-item>
  70. <cl-form-item :label="t('身高')" prop="height" required>
  71. <cl-slider v-model="formData.height" :max="220" show-value>
  72. <template #value="{ value }">
  73. <cl-text
  74. :pt="{
  75. className: 'text-center w-[120rpx]'
  76. }"
  77. >{{ value }} cm</cl-text
  78. >
  79. </template>
  80. </cl-slider>
  81. </cl-form-item>
  82. <cl-form-item :label="t('体重')" prop="weight" required>
  83. <cl-slider v-model="formData.weight" :max="150" show-value>
  84. <template #value="{ value }">
  85. <cl-text
  86. :pt="{
  87. className: 'text-center w-[120rpx]'
  88. }"
  89. >{{ value }} kg</cl-text
  90. >
  91. </template>
  92. </cl-slider>
  93. </cl-form-item>
  94. <cl-form-item :label="t('标签')" prop="tags" required>
  95. <view class="flex flex-row flex-wrap">
  96. <cl-checkbox
  97. v-model="formData.tags"
  98. v-for="(item, index) in tagsOptions"
  99. :key="index"
  100. :value="index"
  101. :pt="{
  102. className: 'mr-5 mt-2'
  103. }"
  104. >{{ item.label }}</cl-checkbox
  105. >
  106. </view>
  107. </cl-form-item>
  108. <cl-form-item :label="t('性别')" prop="gender" required>
  109. <cl-select v-model="formData.gender" :options="genderOptions"></cl-select>
  110. </cl-form-item>
  111. <cl-form-item :label="t('所在地区')" prop="pca" required>
  112. <cl-cascader v-model="formData.pca" :options="pcaOptions"></cl-cascader>
  113. </cl-form-item>
  114. <cl-form-item :label="t('出生年月')" prop="birthday" required>
  115. <cl-select-date v-model="formData.birthday" type="date"></cl-select-date>
  116. </cl-form-item>
  117. <cl-form-item :label="t('个人简介')" prop="description">
  118. <cl-textarea
  119. v-model="formData.description"
  120. :placeholder="t('请输入个人简介')"
  121. :maxlength="200"
  122. ></cl-textarea>
  123. </cl-form-item>
  124. <cl-form-item :label="t('公开状态')">
  125. <cl-switch v-model="formData.isPublic"></cl-switch>
  126. </cl-form-item>
  127. </cl-form>
  128. </demo-item>
  129. <demo-item>
  130. <cl-text pre-wrap :pt="{ className: 'text-sm p-2' }">{{
  131. JSON.stringify(formData, null, 4)
  132. }}</cl-text>
  133. </demo-item>
  134. </view>
  135. <cl-footer>
  136. <view class="flex flex-row">
  137. <cl-button
  138. type="light"
  139. size="large"
  140. text
  141. border
  142. :pt="{ className: 'flex-1 !rounded-xl h-[80rpx]' }"
  143. @click="reset"
  144. >{{ t("重置") }}</cl-button
  145. >
  146. <cl-button
  147. type="primary"
  148. :loading="saving"
  149. size="large"
  150. :pt="{ className: 'flex-1 !rounded-xl h-[80rpx]' }"
  151. @click="submit"
  152. >{{ t("提交") }}</cl-button
  153. >
  154. </view>
  155. </cl-footer>
  156. </cl-page>
  157. </template>
  158. <script setup lang="ts">
  159. import { ref, type Ref } from "vue";
  160. import DemoItem from "../components/item.uvue";
  161. import {
  162. useCascader,
  163. useForm,
  164. useUi,
  165. type ClFormRule,
  166. type ClSelectOption
  167. } from "@/uni_modules/cool-ui";
  168. import pca from "@/data/pca.json";
  169. import { t } from "@/locale";
  170. import { dayUts } from "@/cool";
  171. const ui = useUi();
  172. const { formRef, validate, clearValidate } = useForm();
  173. // 性别选项
  174. const genderOptions = [
  175. {
  176. label: t("未知"),
  177. value: 0
  178. },
  179. {
  180. label: t("男"),
  181. value: 1
  182. },
  183. {
  184. label: t("女"),
  185. value: 2
  186. }
  187. ] as ClSelectOption[];
  188. // 标签选项
  189. const tagsOptions = [
  190. {
  191. label: t("篮球"),
  192. value: 1
  193. },
  194. {
  195. label: t("足球"),
  196. value: 2
  197. },
  198. {
  199. label: t("羽毛球"),
  200. value: 3
  201. },
  202. {
  203. label: t("乒乓球"),
  204. value: 4
  205. },
  206. {
  207. label: t("游泳"),
  208. value: 5
  209. }
  210. ] as ClSelectOption[];
  211. // 地区选项
  212. const pcaOptions = useCascader(pca);
  213. type Contact = {
  214. phone: string;
  215. };
  216. // 自定义表单数据类型
  217. type FormData = {
  218. avatarUrl: string;
  219. nickName: string;
  220. email: string;
  221. height: number;
  222. weight: number;
  223. gender: number;
  224. description: string;
  225. pca: string[];
  226. tags: number[];
  227. birthday: string;
  228. isPublic: boolean;
  229. contacts: Contact[];
  230. };
  231. // 表单数据
  232. const formData = ref<FormData>({
  233. avatarUrl: "",
  234. nickName: "神仙都没用",
  235. email: "",
  236. height: 180,
  237. weight: 70,
  238. gender: 0,
  239. description: "",
  240. pca: [],
  241. tags: [1, 2],
  242. birthday: "",
  243. isPublic: false,
  244. contacts: []
  245. }) as Ref<FormData>;
  246. // 表单验证规则
  247. const rules = new Map<string, ClFormRule[]>([
  248. [
  249. "nickName",
  250. [
  251. { required: true, message: t("用户名不能为空") },
  252. { min: 3, max: 20, message: t("用户名长度在3-20个字符之间") }
  253. ]
  254. ],
  255. [
  256. "email",
  257. [
  258. { required: true, message: t("邮箱不能为空") },
  259. { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: t("邮箱格式不正确") }
  260. ]
  261. ],
  262. [
  263. "height",
  264. [
  265. { required: true, message: t("身高不能为空") },
  266. { min: 160, max: 190, message: t("身高在160-190cm之间") }
  267. ]
  268. ],
  269. [
  270. "weight",
  271. [
  272. { required: true, message: t("体重不能为空") },
  273. { min: 40, max: 100, message: t("体重在40-100kg之间") }
  274. ]
  275. ],
  276. [
  277. "tags",
  278. [
  279. { required: true, message: t("标签不能为空") },
  280. { min: 1, max: 2, message: t("标签最多选择2个") }
  281. ]
  282. ],
  283. ["gender", [{ required: true, message: t("性别不能为空") }]],
  284. ["pca", [{ required: true, message: t("所在地区不能为空") }]],
  285. [
  286. "birthday",
  287. [
  288. { required: true, message: t("出生年月不能为空") },
  289. {
  290. validator(value) {
  291. if (dayUts(value).isAfter(dayUts("2010-01-01"))) {
  292. return t("出生年月不大于2010-01-01");
  293. }
  294. return true;
  295. }
  296. }
  297. ]
  298. ],
  299. [
  300. "contacts",
  301. [
  302. {
  303. required: true,
  304. message: t("联系人不能为空")
  305. }
  306. ]
  307. ]
  308. ]);
  309. // 是否保存中
  310. const saving = ref(false);
  311. // 重置表单数据
  312. function reset() {
  313. formData.value.avatarUrl = "";
  314. formData.value.nickName = "";
  315. formData.value.email = "";
  316. formData.value.height = 180;
  317. formData.value.weight = 70;
  318. formData.value.gender = 0;
  319. formData.value.description = "";
  320. formData.value.pca = [];
  321. formData.value.tags = [];
  322. formData.value.birthday = "";
  323. formData.value.isPublic = false;
  324. formData.value.contacts = [];
  325. clearValidate();
  326. }
  327. // 提交表单
  328. function submit() {
  329. validate((valid, errors) => {
  330. if (valid) {
  331. saving.value = true;
  332. setTimeout(() => {
  333. ui.showToast({
  334. message: t("提交成功"),
  335. icon: "check-line"
  336. });
  337. saving.value = false;
  338. reset();
  339. }, 2000);
  340. } else {
  341. ui.showToast({
  342. message: errors[0].message
  343. });
  344. }
  345. });
  346. }
  347. function addContact() {
  348. formData.value.contacts.push({
  349. phone: ""
  350. });
  351. }
  352. function removeContact(index: number) {
  353. formData.value.contacts.splice(index, 1);
  354. }
  355. </script>