App.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <script setup>
  2. import { ref, onMounted } from 'vue';
  3. import { service } from './util/http';
  4. import { message, getLang } from './util/locale';
  5. import { store } from './util/store';
  6. const model = ref({
  7. type: '',
  8. forward: '',
  9. index: 0,
  10. speak: 0,
  11. welcome: [],
  12. inputs: [],
  13. focus: {
  14. index: 0,
  15. auto: true,
  16. },
  17. submit: false,
  18. invalid: '',
  19. forget: false,
  20. interval: 0,
  21. });
  22. const inputs = { 'in': ['username', 'password'], 'up': ['username', 'nick', 'password', 'repeat'], 'forget': ['username', 'email', 'code', 'newpass', 'repeat'] };
  23. const language = (lang) => {
  24. store.language = lang;
  25. localStorage.setItem('language', lang);
  26. window.location.reload();
  27. };
  28. const type = (i, j) => {
  29. if (model.value.interval)
  30. clearInterval(model.value.interval);
  31. if ((i === 0 && j === 2) || (i === 1 && j === 1)) {
  32. model.value = {
  33. type: i === 1 && j === 1 ? 'forget' : (model.value.type === 'up' || model.value.type === 'forget' ? 'in' : 'up'),
  34. forward: model.value.forward,
  35. index: 0,
  36. speak: 0,
  37. welcome: [],
  38. inputs: [],
  39. focus: {
  40. index: 0,
  41. auto: true,
  42. },
  43. submit: false,
  44. invalid: '',
  45. forget: false,
  46. };
  47. model.value.interval = setInterval(next, model.value.type === 'in' ? 50 : 100);
  48. }
  49. };
  50. const next = () => {
  51. if (model.value.submit || speak())
  52. return;
  53. model.value.index++;
  54. model.value.speak = 0;
  55. };
  56. const speak = () => {
  57. if (model.value.index === 0) {
  58. let welcome = message('welcome.' + model.value.type);
  59. if (model.value.speak > welcome.length)
  60. return false;
  61. model.value.welcome = welcome.substring(0, model.value.speak++).split('\n');
  62. return true;
  63. }
  64. let index = model.value.index - 1;
  65. if (model.value.focus.auto)
  66. model.value.focus.index = index;
  67. if (index < inputs[model.value.type].length) {
  68. let label = message(inputs[model.value.type][index]);
  69. if (index === 0) {
  70. label = message('username.' + model.value.type);
  71. } else if (index === 1 && model.value.type === 'in')
  72. label += '\n' + message('forget');
  73. if (model.value.speak > label.length) {
  74. if (model.value.inputs[index].step === 0)
  75. model.value.inputs[index].step = 1;
  76. return model.value.inputs[index].step < 3;
  77. }
  78. if (model.value.inputs.length < model.value.index) {
  79. let input = {
  80. label: [],
  81. value: '',
  82. placeholder: '',
  83. invalid: '',
  84. step: 0,
  85. password: index >= (model.value.type === 'up' ? 2 : (model.value.type === 'in' ? 1 : 3)),
  86. };
  87. if (index === 0) {
  88. if (model.value.type === 'in')
  89. input.placeholder = message('username-email');
  90. else if (model.value.type === 'up')
  91. input.placeholder = message('username.placeholder');
  92. }
  93. model.value.inputs.push(input);
  94. }
  95. model.value.inputs[index].label = label.substring(0, model.value.speak++).split('\n');
  96. return true;
  97. }
  98. return true;
  99. };
  100. const input = (index) => {
  101. let value = model.value.inputs[index].value;
  102. length = value.length;
  103. if (index === 0) {
  104. if (model.value.type === 'up') {
  105. model.value.inputs[index].invalid = length < 4 || length > 15 ? message('username.invalid.up') : '';
  106. if (!model.value.inputs[index].invalid) {
  107. service('/user/auth/exists', { uid: value }, data => {
  108. model.value.inputs[index].step = data ? 2 : 1;
  109. model.value.inputs[index].invalid = data ? '' : message('username.invalid.up');
  110. });
  111. }
  112. } else {
  113. model.value.inputs[index].invalid = '';
  114. model.value.inputs[index].step = 2;
  115. }
  116. } else if (index === 1) {
  117. if (model.value.type === 'up') {
  118. model.value.inputs[index].invalid = length < 2 || length > 14 ? message('nick.invalid') : '';
  119. model.value.inputs[index].step = length < 2 || length > 14 ? 1 : 2;
  120. } else if (model.value.type === 'in') {
  121. model.value.inputs[index].invalid = length < 7 || length > 20 ? message('password.invalid') : '';
  122. model.value.inputs[index].step = length < 7 || length > 20 ? 1 : 2;
  123. } else if (model.value.type === 'forget') {
  124. let valid = /^(?:\w+\.?-?)*\w+@(?:\w+\.?-?)*\w+$/.test(value);
  125. model.value.inputs[index].invalid = !valid ? message('email.invalid') : '';
  126. model.value.inputs[index].step = !valid ? 1 : 2;
  127. }
  128. } else if (index === 2) {
  129. if (model.value.type === 'up') {
  130. model.value.inputs[index].invalid = length < 7 || length > 20 ? message('password.invalid') : '';
  131. model.value.inputs[index].step = length < 7 || length > 20 ? 1 : 2;
  132. } else if (model.value.type === 'forget') {
  133. model.value.inputs[index].invalid = length === 8 ? '' : message('code.invalid');
  134. model.value.inputs[index].step = length === 8 ? 2 : 1;
  135. }
  136. } else if (index === 3) {
  137. if (model.value.type === 'up') {
  138. model.value.inputs[index].invalid = value === model.value.inputs[index - 1].value ? '' : message('repeat.invalid');
  139. model.value.inputs[index].step = value === model.value.inputs[index - 1].value ? 2 : 1;
  140. } else if (model.value.type === 'forget') {
  141. model.value.inputs[index].invalid = length < 7 || length > 20 ? message('password.invalid') : '';
  142. model.value.inputs[index].step = length < 7 || length > 20 ? 1 : 2;
  143. }
  144. } else if (index === 4) {
  145. model.value.inputs[index].invalid = value === model.value.inputs[index - 1].value ? '' : message('repeat.invalid');
  146. model.value.inputs[index].step = value === model.value.inputs[index - 1].value ? 2 : 1;
  147. }
  148. };
  149. const confirm = (index, event, callback) => {
  150. if (event)
  151. event.target.blur();
  152. if (model.value.submit) {
  153. model.value.focus = { index: model.value.inputs.length + 1, auto: false };
  154. return;
  155. }
  156. if (model.value.type === 'forget' && !callback) {
  157. if (index === 1) {
  158. service('/home/forget-email', { uid: model.value.inputs[0].value, email: model.value.inputs[1].value, subject: message('forget.title'), lang: getLang() }, data => {
  159. model.value.invalid = message(data ? 'code.send' : 'email.failure');
  160. if (data)
  161. confirm(index, null, 1);
  162. });
  163. return;
  164. }
  165. if (index === 2) {
  166. service('/home/forget-code', { code: model.value.inputs[2].value }, data => {
  167. if (data)
  168. confirm(index, null, 1);
  169. model.value.invalid = data ? '' : message('code.invalid');
  170. });
  171. return;
  172. }
  173. }
  174. model.value.focus.auto = true;
  175. if (model.value.inputs[index].step === 2)
  176. model.value.inputs[index].step = 3;
  177. if (model.value.inputs[index].step === 3) {
  178. model.value.submit = (index === 1 && model.value.type === 'in') || (index === 3 && model.value.type === 'up') || (index === 4 && model.value.type === 'forget');
  179. if (model.value.submit)
  180. model.value.focus = { index: index + 1, auto: false };
  181. }
  182. };
  183. const focus = (index) => {
  184. model.value.focus = { index, auto: false };
  185. };
  186. const submit = () => {
  187. if (model.value.type === 'up') {
  188. service('/user/sign-up', {
  189. type: '',
  190. uid: model.value.inputs[0].value,
  191. nick: model.value.inputs[1].value,
  192. password: model.value.inputs[2].value,
  193. }, data => {
  194. if (data.id) {
  195. forward();
  196. } else {
  197. model.value.invalid = message('sign-up.invalid');
  198. }
  199. }, () => model.value.invalid = message('sign-up.invalid'));
  200. } else if (model.value.type === 'in') {
  201. service('/user/sign-in', {
  202. type: '',
  203. uid: model.value.inputs[0].value,
  204. password: model.value.inputs[1].value,
  205. }, data => {
  206. if (data.id) {
  207. forward();
  208. } else {
  209. model.value.invalid = message('sign-in.invalid');
  210. }
  211. }, () => model.value.invalid = message('sign-in.invalid'));
  212. } else if (model.value.type === 'forget') {
  213. service('/home/password', { code: model.value.inputs[2].value, password: model.value.inputs[3].value }, data => {
  214. model.value.forget = data;
  215. model.value.invalid = message('forget.' + (data ? 'success' : 'failure'));
  216. });
  217. }
  218. };
  219. const forward = () => {
  220. if (model.value.forward === 'parent') {
  221. window.parent.postMessage({ type: 'sign', value: '' }, '*');
  222. window.parent.postMessage({ type: 'session', value: localStorage.getItem('photon-session-id')}, '*')
  223. } else {
  224. location.href = model.value.forward;
  225. }
  226. };
  227. onMounted(() => {
  228. document.title = message('title');
  229. if (location.search && location.search.indexOf('?') > -1) {
  230. for (let param of location.search.substring(1).split('&')) {
  231. if (param.indexOf('type=') === 0) {
  232. model.value.type = param.substring(5);
  233. } else if (param.indexOf('forward=') === 0) {
  234. model.value.forward = decodeURIComponent(param.substring(8));
  235. }
  236. }
  237. }
  238. if (!model.value.type)
  239. model.value.type = 'in';
  240. if (!model.value.forward)
  241. model.value.forward = '/';
  242. model.value.interval = setInterval(next, model.value.type === 'in' ? 50 : 100);
  243. service('/user/sign', {}, data => {
  244. if (data.id){
  245. if(model.value.forward === 'parent'){
  246. window.parent.postMessage({ type: 'sign', value: data.id }, '*');
  247. window.parent.postMessage({ type: 'session', value: localStorage.getItem('photon-session-id')}, '*')
  248. }else{
  249. location.href = model.value.forward;
  250. }
  251. }
  252. });
  253. });
  254. </script>
  255. <template>
  256. <div class="stars">
  257. <div v-for="i in 6" class="star"></div>
  258. </div>
  259. <div class="sign-in-up">
  260. <div class="nav">
  261. <img src="./assets/logo.png" />
  262. <div class="langs">
  263. <div @click="language('zh')"><span>中</span></div>
  264. <div @click="language('en')"><span>En</span></div>
  265. <div @click="language('jp')"><span>あ</span></div>
  266. </div>
  267. </div>
  268. <div>
  269. <div class="dialog">
  270. <div v-for="welcome in model.welcome" class="welcome">{{ welcome }}</div>
  271. <template v-for="(ip, index) in model.inputs">
  272. <div class="label">
  273. <span v-for="(label, li) in ip.label" :class="'label-' + (li === 0 ? '0' : (index + '' + li))"
  274. @click="type(index, li)">{{ label }}</span>
  275. </div>
  276. <div v-if="ip.step > 0" class="input">
  277. <div v-if="model.focus.index === index" class="arrow">→</div>
  278. <div v-else-if="model.type === 'up'" class="correct">√</div>
  279. <input :type="ip.password ? 'password' : 'text'" v-model="model.inputs[index].value"
  280. :placeholder="model.inputs[index].placeholder" @focus="focus(index)" @input="input(index)"
  281. @keypress.enter="confirm(index, $event)" />
  282. <div v-if="model.focus.index === index"
  283. :class="model.inputs[index].step === 2 ? 'button-valid' : 'button-invalid'" @click="confirm(index)">{{
  284. message('continue') }}</div>
  285. <div v-else class="button-empty">{{ message('continue') }}</div>
  286. </div>
  287. <div v-else-if="ip.step === 3" class="input">
  288. <div v-if="model.type === 'up'" class="correct">√</div>
  289. <div class="value">{{ model.inputs[index].password ? '********' : model.inputs[index].value }}</div>
  290. </div>
  291. </template>
  292. <div v-if="model.forget" class="submit" @click="type(0, 2)">{{ message('to-in') }}</div>
  293. <div v-else-if="model.submit" class="submit" @click="submit">{{ message('sign-' + model.type) }}</div>
  294. </div>
  295. <div v-if="model.inputs.length > 0 && model.inputs.length >= model.index" class="invalid">{{
  296. model.inputs[model.index - 1].invalid }}</div>
  297. <div v-if="model.invalid" class="invalid">{{ model.invalid }}</div>
  298. </div>
  299. </div>
  300. </template>
  301. <style scoped>
  302. .stars,
  303. .star {
  304. position: absolute;
  305. top: 0;
  306. right: 0;
  307. bottom: 0;
  308. left: 0;
  309. overflow: hidden;
  310. }
  311. .stars {
  312. background-color: #040d21;
  313. background-image: url(./assets/hero-glow.svg);
  314. background-size: cover;
  315. background-position: center center;
  316. }
  317. .star {
  318. background-image: radial-gradient(2px 2px at 50px 200px, #eee, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 40px 70px, #fff, rgba(0, 0, 0, 0)), radial-gradient(3px 4px at 120px 40px, #ddd, rgba(0, 0, 0, 0));
  319. background-repeat: repeat;
  320. background-size: 250px 250px;
  321. opacity: 0;
  322. animation: zoom 10s infinite;
  323. }
  324. .star:nth-child(1) {
  325. background-position: 10% 90%;
  326. animation-delay: 0s
  327. }
  328. .star:nth-child(2) {
  329. background-position: 20% 50%;
  330. background-size: 270px 500px;
  331. animation-delay: .3s
  332. }
  333. .star:nth-child(3) {
  334. background-position: 40% -80%;
  335. animation-delay: 1.2s
  336. }
  337. .star:nth-child(4) {
  338. background-position: -20% -30%;
  339. transform: rotate(60deg);
  340. animation-delay: 2.5s
  341. }
  342. .star:nth-child(5) {
  343. background-image: radial-gradient(2px 2px at 10px 100px, #eee, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 20px 10px, #fff, rgba(0, 0, 0, 0)), radial-gradient(3px 4px at 150px 40px, #ddd, rgba(0, 0, 0, 0));
  344. background-position: 80% 30%;
  345. animation-delay: 4s
  346. }
  347. .star:nth-child(6) {
  348. background-position: 50% 20%;
  349. animation-delay: 6s
  350. }
  351. @keyframes zoom {
  352. 0% {
  353. opacity: 0;
  354. transform: scale(0.5);
  355. transform: rotate(5deg);
  356. animation-timing-function: ease-in
  357. }
  358. 85% {
  359. opacity: 1
  360. }
  361. 100% {
  362. opacity: .2;
  363. transform: scale(2.2)
  364. }
  365. }
  366. @media(prefers-reduced-motion) {
  367. .star {
  368. animation: none
  369. }
  370. }
  371. .sign-in-up {
  372. position: absolute;
  373. top: 0;
  374. bottom: 0;
  375. display: flex;
  376. flex-direction: column;
  377. align-items: center;
  378. }
  379. @media screen and (max-width: 1080px) {
  380. .sign-in-up {
  381. left: 0;
  382. right: 0;
  383. }
  384. }
  385. @media screen and (min-width: 1080px) {
  386. .sign-in-up {
  387. left: 20vw;
  388. right: 20vw;
  389. }
  390. }
  391. .nav {
  392. width: calc(100% - 32px);
  393. padding: 16px;
  394. display: flex;
  395. justify-content: space-between;
  396. }
  397. .nav img {
  398. width: 32px;
  399. height: 32px;
  400. }
  401. .nav .langs{
  402. color:#8193b2;
  403. display: flex;
  404. column-gap: 8px;
  405. cursor: pointer;
  406. }
  407. .dialog {
  408. margin: 24px;
  409. padding: 24px;
  410. background-color: #0c162d;
  411. border: 1px solid #202637;
  412. border-radius: 6px;
  413. }
  414. .welcome {
  415. width: 33vw;
  416. min-width: 280px;
  417. color: #8193b2;
  418. text-align: center;
  419. }
  420. .label {
  421. margin-top: 24px;
  422. font-weight: 600;
  423. display: flex;
  424. align-items: center;
  425. }
  426. .label-0 {
  427. color: #00cfc8;
  428. }
  429. .label-01 {
  430. padding-left: 16px;
  431. color: #8193b2;
  432. }
  433. .label-02,
  434. .label-11 {
  435. color: #FF8DC0;
  436. cursor: pointer;
  437. }
  438. .label-11 {
  439. padding-left: 16px;
  440. }
  441. .input {
  442. display: flex;
  443. align-items: center;
  444. padding-top: 4px;
  445. }
  446. .arrow {
  447. color: #ea4aaa;
  448. }
  449. .correct {
  450. color: #20bb3d;
  451. padding-right: 8px;
  452. }
  453. .input input {
  454. flex-grow: 1;
  455. outline: none;
  456. border: 1px solid #0c162d;
  457. background: none;
  458. color: #fff;
  459. }
  460. .input input:focus {
  461. border: 1px solid rgb(9, 105, 218);
  462. }
  463. .value {
  464. color: #fff;
  465. }
  466. .button-invalid,
  467. .button-valid,
  468. .button-empty {
  469. padding: 4px 8px;
  470. border-radius: 6px;
  471. white-space: nowrap;
  472. }
  473. .button-invalid {
  474. color: #627597;
  475. border: 1px solid #627597;
  476. cursor: default;
  477. }
  478. .button-valid,
  479. .submit {
  480. color: #FF8DC0;
  481. border: 1px solid #FF8DC0;
  482. cursor: pointer;
  483. }
  484. .button-empty {
  485. color: #0c162d;
  486. border: 1px solid #0c162d;
  487. cursor: default;
  488. }
  489. .submit {
  490. text-align: center;
  491. padding: 8px 0;
  492. border-radius: 6px;
  493. margin-top: 24px;
  494. }
  495. .invalid {
  496. padding: 0 48px;
  497. color: #8193b2;
  498. }
  499. </style>