register.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <template>
  2. <!-- 注册 -->
  3. <div class="flex-col registerPage">
  4. <s-header :name="$t('register.header')" :noback="false"></s-header>
  5. <div class="registerFormBox">
  6. <van-form @submit="registerSubmit">
  7. <van-field v-model="username" name="username" :label="$t('register.usernameLabel')"
  8. :placeholder="$t('register.usernamePlaceholder')"
  9. :rules="[{ pattern: /^[a-zA-Z][a-zA-Z0-9]*$/, message: $t('register.usernameRequired') }]" />
  10. <br>
  11. <van-field v-model="name" name="name" :label="$t('register.nameLabel')"
  12. :placeholder="$t('register.namePlaceholder')"
  13. :rules="[{ required: true, message: $t('register.nameRequired') }]" />
  14. <br>
  15. <!-- 密码 -->
  16. <van-field v-model="password" name="password" type="password" :label="$t('register.passwordLabel')"
  17. :placeholder="$t('register.passwordPlaceholder')" :rules="[
  18. { required: true, message: $t('register.passwordRequired') },
  19. { pattern: /^(?=.*[a-zA-Z])(?=.*\d).{10,}$/, message: $t('register.passwordPattern') }
  20. ]" />
  21. <br>
  22. <!-- 确认密码 -->
  23. <van-field v-model="passwordCheck" name="passwordCheck" type="password"
  24. :label="$t('register.passwordCheckLabel')" :placeholder="$t('register.passwordCheckPlaceholder')" :rules="[
  25. { required: true, message: $t('register.passwordCheckRequired') },
  26. ]" />
  27. <br>
  28. <!-- 国家或地区 -->
  29. <div class="van-cell van-field">
  30. <div class="van-cell__title van-field__label"><span>{{ $t('register.country') }}</span></div>
  31. <div class="van-cell__value van-field__value radioBox">
  32. <van-radio-group v-model="ifForeign" direction="horizontal">
  33. <van-radio name="0">{{ $t('register.chinese') }}</van-radio>
  34. <van-radio name="1">{{ $t('register.other') }}</van-radio>
  35. </van-radio-group>
  36. </div>
  37. </div>
  38. <!-- 国家城市 -->
  39. <van-field v-model="areaValue" v-if="ifForeign === '0'" readonly name="picker" :label="$t('register.areaLabel')"
  40. :placeholder="$t('register.areaPlaceholder')" @click="showArea = true"
  41. :rules="[{ required: true, message: $t('register.areaRequired') }]" />
  42. <van-popup v-model:show="showArea" position="bottom">
  43. <van-cascader :title="$t('register.areaRequired')" :options="areaOptions" @close="showArea = false"
  44. @finish="onConfirmArea" />
  45. </van-popup>
  46. <van-field v-model="cityValue" v-if="ifForeign === '1'" readonly name="picker"
  47. :label="$t('register.countryCity')" :placeholder="$t('register.clickCountryCity')" @click="showCountry = true"
  48. :rules="[{ required: true, message: $t('register.chooseCountryCity') }]" />
  49. <van-popup v-model:show="showCountry" round position="bottom">
  50. <van-cascader :title="$t('register.chooseCountryCity')" :options="countryOptions" @close="showCountry = false"
  51. @finish="onConfirmCountry">
  52. <template #options-top="{ tabIndex }" >
  53. <van-search v-if="tabIndex === 0" v-model="searchValue" class="searchCity" :placeholder="$t('kSelectPop.searchKey')" @update:model-value="valueChange(tabIndex)"/>
  54. </template>
  55. </van-cascader>
  56. </van-popup>
  57. <br>
  58. <!-- 中国 -->
  59. <div v-if="ifForeign === '0'">
  60. <div class="van-cell van-field">
  61. <div class="van-cell__title van-field__label"><span>{{ $t('register.logonMode') }}</span></div>
  62. <div class="van-cell__value van-field__value radioBox">
  63. <van-radio-group class="o-pr-18" v-model="logonMode" direction="horizontal">
  64. <van-radio name="10" :value="0">{{ $t('register.phoneRegistration') }}</van-radio>
  65. <van-radio name="11" :value="1">{{ $t('register.emailRegistration') }}</van-radio>
  66. </van-radio-group>
  67. </div>
  68. </div>
  69. <!-- 国内手机 -->
  70. <van-field v-if="logonMode === '10'" v-model="phone" name="phone" type="tel"
  71. :label="$t('register.phoneLabel')" :placeholder="$t('register.phonePlaceholder')"
  72. :rules="[{ required: ifForeign === '0' && logonMode === '10', pattern: /^1[3456789]\d{9}$/, message: $t('register.phoneRequired') }]" />
  73. <br v-if="ifForeign === '0' && logonMode === '10'">
  74. <!-- 短信验证码 -->
  75. <van-field v-if="logonMode === '10'" v-model="code" name="code" :label="$t('register.codeLabel')"
  76. :placeholder="$t('register.codePlaceholder')"
  77. :rules="[{ required: true, message: $t('register.codeRequired') }]">
  78. <template #button v-if="ifForeign === '0' && logonMode === '10'">
  79. <van-button size="small" type="primary" @click="seedVerCode()"
  80. :disabled="time !== 0 || phone.length === 0" :loading=reqApi :loading-text="$t('register.sending')">{{
  81. time === 0 ?
  82. $t('register.seedVerCode') : time + $t('register.replaysInSeconds')
  83. }}
  84. </van-button>
  85. </template>
  86. </van-field>
  87. <!-- 国内邮箱 -->
  88. <van-field v-if="logonMode === '11'" v-model="email" name="email" :label="$t('register.emailLabel')"
  89. :placeholder="$t('register.emailPlaceholder')" :rules="[
  90. { required: ifForeign === '0' && logonMode === '11', message: $t('register.emailRequired') },
  91. { pattern: /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/, message: '邮箱格式错误!' }
  92. ]" :minlength="3" :maxlength="20">
  93. </van-field>
  94. <br v-if="ifForeign === '0' && logonMode === '11'">
  95. <!-- 国内邮箱验证码 -->
  96. <van-field v-if="logonMode === '11'" v-model="code" name="code" :label="$t('register.emailCodeLabel')"
  97. :placeholder="$t('register.emailCodePlaceholder')"
  98. :rules="[{ required: true, message: $t('register.emailCodeRequired') }]">
  99. <template #button v-if="ifForeign === '0' && logonMode === '11'">
  100. <van-button size="small" type="primary" @click="seedVerCode()"
  101. :disabled="time !== 0 || email.length === 0" :loading=reqApi :loading-text="$t('register.sending')">{{
  102. time === 0 ?
  103. $t('register.seedVerCode') : time + $t('register.replaysInSeconds')
  104. }}
  105. </van-button>
  106. </template>
  107. </van-field>
  108. </div>
  109. <!-- 海外 -->
  110. <div v-if="ifForeign === '1'">
  111. <!-- 海外邮箱 -->
  112. <van-field v-if="ifForeign === '1'" v-model="email" name="email" :label="$t('register.emailLabel')"
  113. :placeholder="$t('register.emailPlaceholder')"
  114. :rules="[{ required: ifForeign === '1', message: $t('register.emailRequired') }]" />
  115. <br v-if="ifForeign === '1'">
  116. <!-- 海外邮箱验证码 -->
  117. <van-field v-if="ifForeign === '1'" v-model="code" name="code" :label="$t('register.emailCodeLabel')"
  118. :placeholder="$t('register.emailCodePlaceholder')"
  119. :rules="[{ required: true, message: $t('register.emailCodeRequired') }]">
  120. <template #button v-if="ifForeign === '1'">
  121. <van-button size="small" type="primary" @click="seedVerCode()"
  122. :disabled="time !== 0 || email.length === 0" :loading=reqApi :loading-text="$t('register.sending')">{{
  123. time === 0 ?
  124. $t('register.seedVerCode') : time + $t('register.replaysInSeconds')
  125. }}
  126. </van-button>
  127. </template>
  128. </van-field>
  129. </div>
  130. <br>
  131. <van-field v-model="inviteCode" name="inviteCode" :label="$t('register.invitationCode')"
  132. :placeholder="$t('register.invitationCodePlaceholder')" :rules="[{ required: true, message: $t('register.invitationCodePlaceholder') }]" />
  133. <!-- 提交验证信息 -->
  134. <van-button round type="primary" class="register" native-type="submit">{{
  135. $t('register.registerButton')
  136. }}
  137. </van-button>
  138. </van-form>
  139. </div>
  140. </div>
  141. </template>
  142. <script>
  143. import md5 from 'js-md5';
  144. import { ref, onMounted, reactive, toRefs, watch } from 'vue';
  145. import { showFailToast, showToast } from 'vant';
  146. // import { sentRegisterCode, tAdminSave, getLastSendTime } from '@/service/register';
  147. import { sentRegisterCode, tAdminSave } from '@/service/register';
  148. import sHeader from '@/components/SimpleHeader';
  149. import logiLogoImgUrl from "@/assets/login/logo.png";
  150. import { useRouter } from 'vue-router';
  151. import { getLocal, setLocal, styleUrl } from '@/common/js/utils';
  152. import { useI18n } from "vue-i18n";
  153. import { countriesData } from '@/common/js/countries';
  154. import { countriesDataEn } from '@/common/js/countries-en';
  155. import { useCascaderAreaData } from '@vant/area-data';
  156. export default {
  157. setup() {
  158. // 引入语言
  159. const languageName = ref(getLocal("curLang"));
  160. const { t } = useI18n();
  161. const active = ref(0);
  162. const username = ref('');
  163. const name = ref('');
  164. const password = ref('');
  165. const passwordCheck = ref('');
  166. const ifForeign = ref('0');
  167. const logonMode = ref('10');
  168. const phone = ref('');
  169. const email = ref('');
  170. const code = ref('');
  171. const inviteCode = ref('');
  172. const verifyRef = ref(null);
  173. const verCodeTime = reactive({
  174. time: 0
  175. });
  176. let phoneOrEmailStr = ref('');
  177. const router = useRouter();
  178. const reqApi = ref(false);
  179. const hostName = ref('');
  180. const citiesValue = ref('');
  181. const showCountry = ref(false);
  182. const cityValue = ref('');
  183. const countryOptions = ref(languageName.value == 'zh' ? countriesData : countriesDataEn);
  184. const { locale } = useI18n()
  185. watch(locale, (newValue) => {
  186. if (newValue == 'zh') {
  187. countryOptions.value = countriesData;
  188. } else {
  189. countryOptions.value = countriesDataEn;
  190. }
  191. });
  192. const onConfirmCountry = ({ selectedOptions }) => {
  193. cityValue.value = selectedOptions.map((option) => option.text).join('/');
  194. citiesValue.value = selectedOptions[1]?.value;
  195. showCountry.value = false;
  196. };
  197. const areaOptions = ref();
  198. const areaValue = ref('');
  199. const showArea = ref(false);
  200. const onConfirmArea = ({ selectedOptions }) => {
  201. if (selectedOptions[0]?.text == selectedOptions[1]?.text) {
  202. areaValue.value = selectedOptions[0]?.text;
  203. citiesValue.value = selectedOptions[0]?.text;
  204. } else {
  205. areaValue.value = selectedOptions[0]?.text + "/" + selectedOptions[1]?.text;
  206. citiesValue.value = selectedOptions[0]?.text + selectedOptions[1]?.text;
  207. }
  208. showArea.value = false;
  209. };
  210. // 注册点击
  211. const registerSubmit = async () => {
  212. if (password.value !== passwordCheck.value) {
  213. showFailToast(t('register.twoTypedDiff'));
  214. return false;
  215. }
  216. if (username.value ==='admin') {
  217. showFailToast(t('register.A0201'));
  218. return false;
  219. }
  220. const { data } = await tAdminSave({
  221. username: username.value,
  222. name: name.value,
  223. password: md5(password.value),
  224. ifForeign: ifForeign.value,
  225. phoneOrEmail: phone.value || email.value,
  226. code: code.value,
  227. companyType: '0',
  228. inviteCode: inviteCode.value,
  229. cities: citiesValue.value
  230. });
  231. if (data.code === '00000') {
  232. showToast(t('register.registerSucess'));
  233. router.push({ path: '/login' });
  234. } else if (data.code === 'R0001') {
  235. showFailToast(t('register.R0001'));
  236. } else if (data.code === 'R0002') {
  237. showFailToast(t('register.R0002'));
  238. } else if (data.code === 'R0003') {
  239. showFailToast(t('register.R0003'));
  240. } else if (data.code === 'R0004') {
  241. showFailToast(t('register.R0004'));
  242. } else if (data.code === 'R0005') {
  243. showFailToast(t('register.R0005'));
  244. } else if (data.code === 'R0006') {
  245. showFailToast(t('register.R0006'));
  246. } else if (data.code === 'A0201') {
  247. showFailToast(t('register.A0201'));
  248. } else if (data.code === 'A0203') {
  249. showFailToast(t('register.A0203'));
  250. } else {
  251. showFailToast(t('register.registerFail'));
  252. }
  253. };
  254. // 发送验证码
  255. const seedVerCode = async () => {
  256. reqApi.value = true;
  257. if (ifForeign.value === '1') {
  258. phoneOrEmailStr = email.value;
  259. } else if (ifForeign.value === '0' && logonMode.value === '10') {
  260. phoneOrEmailStr = phone.value;
  261. } else {
  262. phoneOrEmailStr = email.value;
  263. }
  264. getCurrentDomain();
  265. try {
  266. const { data } = await sentRegisterCode({
  267. ifForeign: ifForeign.value,
  268. phoneOrEmail: phoneOrEmailStr,
  269. hostName: hostName.value,
  270. });
  271. reqApi.value = false;
  272. if (data.code === '00000') {
  273. showToast(data.data);
  274. verCodeTime.time = 1 * 60; // 1分钟定时器,60s后可以更换验证方式
  275. verCodeTimeInterval();
  276. } else if (data.code === 'R0009') {
  277. showToast(t('register.R0009'));
  278. } else if (data.code === 'R0008') {
  279. showToast(t('register.R0008'));
  280. } else if (data.code === 'A0202') {
  281. showToast(t('register.A0202'));
  282. } else if (data.code === 'A0207') {
  283. showToast(t('register.A0207'));
  284. } else if (data.code === 'R0004') {
  285. showToast(t('register.R0004'));
  286. } else {
  287. showToast(data.message);
  288. }
  289. } catch (error) {
  290. reqApi.value = false;
  291. }
  292. }
  293. // 验证码发送成功开始1分钟倒计时
  294. const verCodeTimeInterval = () => {
  295. const intervalId = setInterval(() => {
  296. verCodeTime.time--;
  297. setLocal('registerVerCodeTime', verCodeTime.time);
  298. if (verCodeTime.time === 0) {
  299. clearInterval(intervalId); // 清除定时器
  300. }
  301. }, 1000);
  302. }
  303. // 初始化页面获取验证码倒计时
  304. onMounted(async () => {
  305. // 加载样式
  306. styleUrl('register');
  307. verCodeTime.time = getLocal('registerVerCodeTime');
  308. if (verCodeTime.time && verCodeTime.time !== '') {
  309. verCodeTime.time = parseInt(verCodeTime.time);
  310. if (verCodeTime.time > 0) {
  311. verCodeTimeInterval();
  312. }
  313. } else {
  314. verCodeTime.time = 0;
  315. }
  316. const areaData = useCascaderAreaData();
  317. areaData.forEach(province => {
  318. province.children.forEach(city => {
  319. // 删除城市中的区级信息
  320. delete city.children;
  321. });
  322. });
  323. // 删除台湾、澳门和香港
  324. const provincesToRemove = ['710000', '810000', '820000'];
  325. areaOptions.value = areaData.filter(province => !provincesToRemove.includes(province.value));
  326. });
  327. const getCurrentDomain = () => {
  328. const currentDomain = window.location.href;
  329. switch (true) {
  330. case currentDomain.includes('sevencloud.com.cn'):
  331. hostName.value = 'Sevencloud';
  332. break;
  333. case currentDomain.includes('portalmcc.com.cn'):
  334. hostName.value = 'Portalmcc';
  335. break;
  336. default:
  337. hostName.value = 'Sunzee';
  338. }
  339. }
  340. const signOptions = [
  341. { text: '手机注册', value: "mo" },
  342. { text: '邮箱注册', value: "ema" }
  343. ]
  344. // 搜索关键词
  345. const searchValue = ref('');
  346. const countryData = ref(languageName.value == 'zh' ? countriesData : countriesDataEn);
  347. // 搜索数据
  348. const valueChange = (index) => {
  349. let tempOptions = [];
  350. if (searchValue.value) {
  351. if (index === 0) {
  352. // 国家
  353. countryData.value.forEach(item => {
  354. if (item.text.includes(searchValue.value)) {
  355. tempOptions.push(item);
  356. }
  357. });
  358. countryOptions.value = tempOptions;
  359. }
  360. } else {
  361. countryOptions.value = countryData.value;
  362. }
  363. };
  364. return {
  365. ...toRefs(verCodeTime),
  366. logiLogoImgUrl,
  367. username,
  368. name,
  369. password,
  370. passwordCheck,
  371. ifForeign,
  372. phone,
  373. email,
  374. code,
  375. inviteCode,
  376. verifyRef,
  377. verCodeTime,
  378. verCodeTimeInterval,
  379. seedVerCode,
  380. registerSubmit,
  381. signOptions,
  382. signinModel: 'aaabb',
  383. active,
  384. logonMode,
  385. reqApi,
  386. cityValue,
  387. showCountry,
  388. countryOptions,
  389. onConfirmCountry,
  390. showArea,
  391. areaOptions,
  392. // areaList,
  393. onConfirmArea,
  394. areaValue,
  395. searchValue,
  396. valueChange
  397. }
  398. },
  399. components: { sHeader }
  400. }
  401. </script>
  402. <style lang="less" scoped>
  403. @import '../common/style/mixin';
  404. </style>