create-pubinfo 2.0.0-beta.2 → 2.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/index.cjs +173 -125
  2. package/dist/index.js +167 -120
  3. package/package.json +3 -2
  4. package/templates/pubinfo-template/.browserslistrc +5 -0
  5. package/templates/pubinfo-template/.editorconfig +10 -0
  6. package/templates/pubinfo-template/.env +12 -0
  7. package/templates/pubinfo-template/.env.development +6 -0
  8. package/templates/pubinfo-template/.env.production +10 -0
  9. package/templates/pubinfo-template/commitlint.config.js +66 -0
  10. package/templates/pubinfo-template/eslint.config.ts +3 -0
  11. package/templates/pubinfo-template/index.html +47 -0
  12. package/templates/pubinfo-template/openapi.config.ts +33 -0
  13. package/templates/pubinfo-template/package.json +52 -0
  14. package/templates/pubinfo-template/pubinfo.config.ts +9 -0
  15. package/templates/pubinfo-template/public/browser_upgrade/chrome.png +0 -0
  16. package/templates/pubinfo-template/public/browser_upgrade/edge.png +0 -0
  17. package/templates/pubinfo-template/public/browser_upgrade/index.css +49 -0
  18. package/templates/pubinfo-template/public/loading.css +92 -0
  19. package/templates/pubinfo-template/src/App.vue +15 -0
  20. package/templates/pubinfo-template/src/api/modules/auth/index.ts +3 -0
  21. package/templates/pubinfo-template/src/api/modules/auth/renzhengfuwu.ts +145 -0
  22. package/templates/pubinfo-template/src/api/modules/auth/typings.d.ts +97 -0
  23. package/templates/pubinfo-template/src/api/request.ts +124 -0
  24. package/templates/pubinfo-template/src/assets/icons/logo.svg +1 -0
  25. package/templates/pubinfo-template/src/assets/icons/process-management.svg +1 -0
  26. package/templates/pubinfo-template/src/assets/icons/workbench.svg +1 -0
  27. package/templates/pubinfo-template/src/assets/images/login-bg.webp +0 -0
  28. package/templates/pubinfo-template/src/assets/images/login-bg_dark.webp +0 -0
  29. package/templates/pubinfo-template/src/assets/images/login-small.png +0 -0
  30. package/templates/pubinfo-template/src/assets/images/login-small_dark.webp +0 -0
  31. package/templates/pubinfo-template/src/components/UIProvider/index.vue +47 -0
  32. package/templates/pubinfo-template/src/layouts/index.vue +38 -0
  33. package/templates/pubinfo-template/src/main.ts +23 -0
  34. package/templates/pubinfo-template/src/modules/auth.ts +21 -0
  35. package/templates/pubinfo-template/src/modules/rbac.ts +52 -0
  36. package/templates/pubinfo-template/src/routes/index.ts +71 -0
  37. package/templates/pubinfo-template/src/routes/modules/demo/breadcrumb.example.ts +62 -0
  38. package/templates/pubinfo-template/src/routes/modules/demo/link.ts +15 -0
  39. package/templates/pubinfo-template/src/routes/modules/demo/multilevel.menu.example.ts +68 -0
  40. package/templates/pubinfo-template/src/routes/modules/demo/other.page.ts +37 -0
  41. package/templates/pubinfo-template/src/routes/modules/demo/single.ts +14 -0
  42. package/templates/pubinfo-template/src/settings.ts +8 -0
  43. package/templates/pubinfo-template/src/stores/index.ts +2 -0
  44. package/templates/pubinfo-template/src/stores/modules/conter.ts +16 -0
  45. package/templates/pubinfo-template/src/views/demo/breadcrumb_example/detail1.vue +11 -0
  46. package/templates/pubinfo-template/src/views/demo/breadcrumb_example/detail2.vue +11 -0
  47. package/templates/pubinfo-template/src/views/demo/breadcrumb_example/list1.vue +11 -0
  48. package/templates/pubinfo-template/src/views/demo/breadcrumb_example/list2.vue +11 -0
  49. package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/level3/page1.vue +11 -0
  50. package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/level3/page2.vue +11 -0
  51. package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/page.vue +11 -0
  52. package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/page.vue +11 -0
  53. package/templates/pubinfo-template/src/views/demo/other_page/des.vue +13 -0
  54. package/templates/pubinfo-template/src/views/demo/other_page/index.vue +25 -0
  55. package/templates/pubinfo-template/src/views/demo/preview-empty/index.vue +17 -0
  56. package/templates/pubinfo-template/src/views/demo/single/index.vue +13 -0
  57. package/templates/pubinfo-template/src/views/system/index.vue +5 -0
  58. package/templates/pubinfo-template/src/views/system/login/components/LoginForm.vue +68 -0
  59. package/templates/pubinfo-template/src/views/system/login/components/LoginWithPhone.vue +213 -0
  60. package/templates/pubinfo-template/src/views/system/login/components/PasswordLogin.vue +199 -0
  61. package/templates/pubinfo-template/src/views/system/login/components/Savephone.vue +17 -0
  62. package/templates/pubinfo-template/src/views/system/login/components/Useragreement.vue +26 -0
  63. package/templates/pubinfo-template/src/views/system/login/composables.ts +84 -0
  64. package/templates/pubinfo-template/src/views/system/login/index.vue +165 -0
  65. package/templates/pubinfo-template/stylelint.config.js +3 -0
  66. package/templates/pubinfo-template/tsconfig.json +3 -0
  67. package/templates/pubinfo-template/uno.config.ts +17 -0
@@ -0,0 +1,213 @@
1
+ <script setup lang="ts">
2
+ import type { SelectProps } from 'ant-design-vue';
3
+ import type { FormInstance, Rule } from 'ant-design-vue/es/form';
4
+ import { useToggle } from '@vueuse/core';
5
+ import { message } from 'ant-design-vue';
6
+ import { useCountdown } from '../composables';
7
+ import Savephone from './Savephone.vue';
8
+
9
+ defineOptions({
10
+ name: 'LoginWithPhone',
11
+ });
12
+
13
+ interface FormState {
14
+ phone: string
15
+ verify: string
16
+ checked: false
17
+ }
18
+ const [verifyDisabled, setVerifyDisabled] = useToggle(true);
19
+ const phoneForm = ref<FormInstance>();
20
+ const { count, start, isStart } = useCountdown();
21
+ const regionCode = ref<number>(0);
22
+ const router = useRouter();
23
+ const route = useRoute();
24
+
25
+ const formState = reactive<FormState>({
26
+ phone: '',
27
+ verify: '',
28
+ checked: false,
29
+ });
30
+
31
+ const rules: Record<string, Rule[]> = {
32
+ phone: [
33
+ {
34
+ validator: checkPhoneIsCorrect,
35
+ whitespace: true,
36
+ trigger: 'change',
37
+ },
38
+ ],
39
+ verify: [
40
+ {
41
+ required: true,
42
+ whitespace: true,
43
+ message: '请输入验证码',
44
+ },
45
+ ],
46
+ };
47
+
48
+ const regionCodeOptins: SelectProps['options'] = [
49
+ {
50
+ value: 0,
51
+ label: '+86 (中国大陆)',
52
+ },
53
+ {
54
+ value: 1,
55
+ label: '+852 (香港行政区)',
56
+ },
57
+ {
58
+ value: 2,
59
+ label: '+853 (澳门行政区)',
60
+ },
61
+ {
62
+ value: 3,
63
+ label: '+886 (台湾省)',
64
+ },
65
+ ];
66
+
67
+ async function checkPhoneIsCorrect(_rule: Rule, value: string) {
68
+ function reject(area: string) {
69
+ setVerifyDisabled(true);
70
+ return Promise.reject(new Error(`请输入正确的${area}手机号`));
71
+ }
72
+ if (!value) {
73
+ setVerifyDisabled(true);
74
+ return Promise.reject(new Error(`请输入手机号`));
75
+ }
76
+ switch (regionCode.value) {
77
+ // 中国大陆
78
+ case 0:
79
+ if (!/^1[3-9]\d{9}$/.test(value)) {
80
+ return reject('中国大陆');
81
+ }
82
+ break;
83
+ // 香港特别行政区
84
+ case 1:
85
+ if (!/^([569])\d{7}$/.test(value)) {
86
+ return reject('香港特别行政区');
87
+ }
88
+ break;
89
+ // 澳门特别行政区
90
+ case 2:
91
+ if (!/^6\d{7}$/.test(value)) {
92
+ return reject('澳门特别行政区');
93
+ }
94
+ break;
95
+ // 台湾省
96
+ case 3:
97
+ if (!/^9\d{8}$/.test(value)) {
98
+ return reject('台湾省');
99
+ }
100
+ break;
101
+ default:
102
+ break;
103
+ }
104
+ setVerifyDisabled(false);
105
+ }
106
+
107
+ async function areaChange() {
108
+ if (!formState.phone) {
109
+ return;
110
+ }
111
+ phoneForm.value?.validateFields('phone');
112
+ }
113
+
114
+ async function sendVerifyCode() {
115
+ message.success('验证码已发送,请注意查收');
116
+ start();
117
+ }
118
+
119
+ async function submit() {
120
+ try {
121
+ await phoneForm.value?.validate();
122
+
123
+ // TODO 实现手机号登录逻辑
124
+
125
+ if (formState.checked) {
126
+ localStorage.setItem('pubinfo_web_savedPhone', formState.phone);
127
+ }
128
+ else {
129
+ localStorage.removeItem('pubinfo_web_savedPhone');
130
+ }
131
+ message.success('登录成功');
132
+ router.push(route.query.redirect?.toString() ?? '/');
133
+ }
134
+ catch (error) {
135
+ }
136
+ }
137
+
138
+ onMounted(() => {
139
+ const phone = localStorage.getItem('pubinfo_web_savedPhone');
140
+ if (phone) {
141
+ formState.phone = phone;
142
+ setVerifyDisabled(false);
143
+ formState.checked = true as any;
144
+ }
145
+ });
146
+ </script>
147
+
148
+ <template>
149
+ <div class="phone-form" mt-40px>
150
+ <a-form
151
+ ref="phoneForm"
152
+ :model="formState"
153
+ autocomplete="off"
154
+ :rules="rules"
155
+ @finish="submit"
156
+ >
157
+ <a-form-item name="phone">
158
+ <a-input-group compact>
159
+ <a-form-item-rest>
160
+ <a-select
161
+ v-model:value="regionCode"
162
+ size="large"
163
+ style="width: 35%;"
164
+ :options="regionCodeOptins"
165
+ @change="areaChange"
166
+ />
167
+ </a-form-item-rest>
168
+ <a-input
169
+ v-model:value="formState.phone"
170
+ allow-clear
171
+ size="large"
172
+ placeholder="请输入手机号"
173
+ style="width: 65%;"
174
+ />
175
+ </a-input-group>
176
+ </a-form-item>
177
+
178
+ <a-form-item name="verify">
179
+ <a-input
180
+ v-model:value="formState.verify"
181
+ allow-clear
182
+ size="large"
183
+ placeholder="请输入验证码"
184
+ >
185
+ <template #suffix>
186
+ <a-button
187
+ size="small"
188
+ type="link"
189
+ :disabled="verifyDisabled"
190
+ >
191
+ <span v-if="!isStart" @click="sendVerifyCode">获取短信验证码</span>
192
+ <span v-else text="#8A8E92">{{ count }}秒后重新获取</span>
193
+ </a-button>
194
+ </template>
195
+ </a-input>
196
+ </a-form-item>
197
+ <a-form-item>
198
+ <Savephone v-model:checked="formState.checked" />
199
+ </a-form-item>
200
+ <a-form-item>
201
+ <a-button
202
+ class="h-48px!"
203
+ block
204
+ type="primary"
205
+ html-type="submit"
206
+ :disabled="!formState.checked"
207
+ >
208
+ 登录
209
+ </a-button>
210
+ </a-form-item>
211
+ </a-form>
212
+ </div>
213
+ </template>
@@ -0,0 +1,199 @@
1
+ <script setup lang="ts">
2
+ import type { FormInstance, Rule } from 'ant-design-vue/es/form';
3
+ import type { UnwrapRef } from 'vue';
4
+ import { useToggle } from '@vueuse/core';
5
+ import { message, Modal } from 'ant-design-vue';
6
+ import { useUserStore } from 'pubinfo';
7
+ import { RESPONSE_CODE } from 'pubinfo/request';
8
+ import { useCaptchas, useFocusElement } from '../composables';
9
+ import Useragreement from './Useragreement.vue';
10
+
11
+ defineOptions({
12
+ name: 'PasswordLogin',
13
+ });
14
+
15
+ interface FormState {
16
+ account: string
17
+ password: string
18
+ captchas: string
19
+ captchaHash: string
20
+ checked: boolean
21
+ }
22
+
23
+ interface HttpError {
24
+ code: RESPONSE_CODE
25
+ data: any
26
+ hint: string
27
+ msg: string
28
+ success: boolean
29
+ }
30
+
31
+ function isHttpError(error: unknown): error is HttpError {
32
+ return (error as HttpError).code !== undefined;
33
+ }
34
+
35
+ const userStore = useUserStore();
36
+ const { send, captcha, captchaHash } = useCaptchas();
37
+ const router = useRouter();
38
+ const route = useRoute();
39
+ const { focusElementDataId } = useFocusElement();
40
+
41
+ const loginForm = ref<FormInstance | null>(null);
42
+ const formState: UnwrapRef<FormState> = reactive({
43
+ account: '',
44
+ password: '',
45
+ captchas: '',
46
+ checked: true,
47
+ captchaHash,
48
+ });
49
+
50
+ const rules: Record<string, Rule[]> = {
51
+ account: [{ required: true, whitespace: true, message: '请输入账号' }],
52
+ password: [{ required: true, whitespace: true, message: '请输入密码' }],
53
+ captchas: [{ required: true, whitespace: true, message: '请输入验证码' }],
54
+ };
55
+
56
+ const [loading, setLoading] = useToggle(false);
57
+
58
+ async function onSubmit() {
59
+ if (loading.value) {
60
+ return;
61
+ }
62
+ setLoading(true);
63
+ try {
64
+ await loginForm.value?.validate();
65
+ await userStore.login(formState);
66
+ message.success('登录成功');
67
+ router.push(route.query.redirect?.toString() ?? '/');
68
+ }
69
+ catch (error) {
70
+ if (isHttpError(error)) {
71
+ const code = error.code;
72
+ switch (code) {
73
+ case RESPONSE_CODE.LOGINNAME_PASSWORD_WRONG:
74
+ case RESPONSE_CODE.CODE_OVERTIME:
75
+ case RESPONSE_CODE.CODE_WRONG:
76
+ send();
77
+ break;
78
+ case RESPONSE_CODE.CHANGE_INIT_PASSWORD:
79
+ case RESPONSE_CODE.PASSWORD_EXPIRED:
80
+ createChangePasswordModal(error.msg, error?.data?.accessToken);
81
+ break;
82
+ default:
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ finally {
88
+ setLoading(false);
89
+ }
90
+ }
91
+
92
+ function createChangePasswordModal(msg: string, token: string) {
93
+ Modal.confirm({
94
+ title: '提示',
95
+ content() {
96
+ return msg;
97
+ },
98
+ okText: '修改密码',
99
+ cancelText: '暂不登录',
100
+ onOk() {
101
+ router.push({
102
+ name: 'ChangePassword',
103
+ params: {
104
+ changePassWordToken: token,
105
+ },
106
+ });
107
+ },
108
+ });
109
+ }
110
+ </script>
111
+
112
+ <template>
113
+ <div class="login-form">
114
+ <AForm ref="loginForm" :model="formState" :rules="rules" @finish="onSubmit">
115
+ <a-form-item name="account">
116
+ <a-input
117
+ v-model:value="formState.account"
118
+ allow-clear
119
+ size="large"
120
+ placeholder="请输入登录账号"
121
+ data-id="account"
122
+ >
123
+ <template #prefix>
124
+ <PubinfoIcon v-if="focusElementDataId === 'account'" name="i-solar-user-bold" color="#1578FF" />
125
+ <PubinfoIcon v-else name="i-solar-user-broken" color="#AFB0B2" />
126
+ </template>
127
+ </a-input>
128
+ </a-form-item>
129
+ <a-form-item name="password">
130
+ <a-input-password
131
+ v-model:value="formState.password"
132
+ allow-clear
133
+ size="large"
134
+ placeholder="请输入登录密码"
135
+ visibility-toggle
136
+ data-id="password"
137
+ >
138
+ <template #prefix>
139
+ <PubinfoIcon v-if="focusElementDataId === 'password'" name="i-solar-lock-password-unlocked-bold" color="#1578FF" />
140
+ <PubinfoIcon v-else name="i-solar-lock-password-unlocked-broken" color="#AFB0B2" />
141
+ </template>
142
+ </a-input-password>
143
+ </a-form-item>
144
+ <a-form-item name="captchas">
145
+ <a-input
146
+ v-model:value="formState.captchas"
147
+ size="large"
148
+ allow-clear
149
+ placeholder="请输入验证码"
150
+ data-id="captchas"
151
+ >
152
+ <template #prefix>
153
+ <PubinfoIcon v-if="focusElementDataId === 'captchas'" name="i-solar-shield-check-bold" color="#1578FF" />
154
+ <PubinfoIcon v-else name="i-solar-shield-check-broken" color="#AFB0B2" />
155
+ </template>
156
+ <template #addonAfter>
157
+ <div
158
+ w-100px
159
+ h-38px
160
+ cursor-pointer
161
+ @click="send"
162
+ >
163
+ <img :src="captcha">
164
+ </div>
165
+ </template>
166
+ </a-input>
167
+ </a-form-item>
168
+ <a-form-item>
169
+ <Useragreement v-model:checked="formState.checked" />
170
+ </a-form-item>
171
+ <!-- 登录 -->
172
+ <a-form-item>
173
+ <div w-full h-48px>
174
+ <a-button
175
+ class="h-48px!"
176
+ type="primary"
177
+ html-type="submit"
178
+ block
179
+ :disabled="!formState.checked"
180
+ :loading="loading"
181
+ >
182
+ 登录
183
+ </a-button>
184
+ </div>
185
+ </a-form-item>
186
+ </AForm>
187
+ </div>
188
+ </template>
189
+
190
+ <style scoped lang="scss">
191
+ .login-form {
192
+ margin-top: 40px;
193
+
194
+ :deep(.ant-input-group-addon) {
195
+ padding: 0 !important;
196
+ overflow: hidden;
197
+ }
198
+ }
199
+ </style>
@@ -0,0 +1,17 @@
1
+ <script setup lang="ts">
2
+ defineOptions({
3
+ name: 'Savephone',
4
+ });
5
+
6
+ const checked = defineModel(
7
+ 'checked',
8
+ {
9
+ type: Boolean, default: false,
10
+ });
11
+ </script>
12
+
13
+ <template>
14
+ <a-checkbox v-model:checked="checked">
15
+ 记住手机号
16
+ </a-checkbox>
17
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ defineOptions({
3
+ name: 'Useragreement',
4
+ });
5
+
6
+ const checked = defineModel(
7
+ 'checked',
8
+ {
9
+ type: Boolean, default: true,
10
+ });
11
+
12
+ function jumpUseragreementPage() {
13
+ // 暂未实现用户协议、隐私政策
14
+ }
15
+ </script>
16
+
17
+ <template>
18
+ <a-checkbox v-model:checked="checked">
19
+ 我已阅读并同意<span
20
+ hover:underline
21
+ decoration-2
22
+ text="#1578FF"
23
+ @click.prevent="jumpUseragreementPage"
24
+ >用户协议、隐私政策</span>
25
+ </a-checkbox>
26
+ </template>
@@ -0,0 +1,84 @@
1
+ import type { Component, MaybeRef } from 'vue';
2
+ import { getAuthLoginValidCode } from '@/api/modules/auth';
3
+ import { useActiveElement } from '@vueuse/core';
4
+ import { useRequest } from 'alova/client';
5
+
6
+ interface LoginTabs {
7
+ title: string
8
+ component: Component
9
+ }
10
+
11
+ export function useLoginTabs() {
12
+ const tabs: LoginTabs[] = [
13
+ {
14
+ title: '账号登录',
15
+ component: defineAsyncComponent(() => import('./components/PasswordLogin.vue')),
16
+ },
17
+ {
18
+ title: '手机号登录',
19
+ component: defineAsyncComponent(() => import('./components/LoginWithPhone.vue')),
20
+ },
21
+ ];
22
+ const initialTab = ref(0);
23
+
24
+ function changeActiveTab(activeTab: number): void {
25
+ initialTab.value = activeTab;
26
+ }
27
+ return {
28
+ tabs,
29
+ initialTab,
30
+ changeActiveTab,
31
+ };
32
+ }
33
+
34
+ export function useCaptchas() {
35
+ const { send, onSuccess } = useRequest(getAuthLoginValidCode({ size: '100x40' }));
36
+ const captcha = ref('');
37
+ const captchaHash = ref('');
38
+
39
+ onSuccess(({ data }) => {
40
+ const { key, plaintext } = data.data!;
41
+
42
+ const imgUrl = `data:image/png;base64,${plaintext}`;
43
+ captcha.value = imgUrl;
44
+ captchaHash.value = key!;
45
+ });
46
+ return {
47
+ send,
48
+ captcha,
49
+ captchaHash,
50
+ };
51
+ }
52
+
53
+ export function useFocusElement() {
54
+ const activeElement = useActiveElement();
55
+ const focusElementDataId = computed(() =>
56
+ activeElement.value?.dataset?.id || 'null',
57
+ );
58
+ return {
59
+ focusElementDataId,
60
+ };
61
+ }
62
+
63
+ export function useCountdown(initialTime: MaybeRef<number> = 60) {
64
+ const count = ref(toValue(initialTime));
65
+ const isStart = computed(() => {
66
+ return toValue(count) !== toValue(initialTime);
67
+ });
68
+
69
+ function start() {
70
+ count.value--;
71
+ const timer = setInterval(() => {
72
+ count.value--;
73
+ if (count.value === 0) {
74
+ clearInterval(timer);
75
+ count.value = toValue(initialTime);
76
+ }
77
+ }, 1000);
78
+ }
79
+ return {
80
+ count,
81
+ start,
82
+ isStart,
83
+ };
84
+ }
@@ -0,0 +1,165 @@
1
+ <script setup lang="ts">
2
+ import LoginFrom from './components/LoginForm.vue';
3
+
4
+ defineOptions({
5
+ name: 'Login',
6
+ });
7
+
8
+ const title = ref(import.meta.env.VITE_APP_TITLE);
9
+
10
+ const designDescription = [
11
+ {
12
+ title: '快速',
13
+ description: '快速搭建技术中台系统',
14
+ },
15
+ {
16
+ title: '灵活',
17
+ description: '灵活配置系统功能',
18
+ },
19
+ {
20
+ title: '便捷',
21
+ description: '高效提升系统开发效率',
22
+ },
23
+ ];
24
+ </script>
25
+
26
+ <template>
27
+ <div class="warp">
28
+ <div class="warp-blank">
29
+ <div
30
+ relative
31
+ top="7vh"
32
+ left="5vw"
33
+ scale="100 sm:100 smd:100 md:100 xl:110 2xl:120"
34
+ transform-origin="top-left"
35
+ transition-all
36
+ hidden
37
+ smd:block
38
+ >
39
+ <div class="logo-group">
40
+ <PubinfoIcon
41
+ name="logo"
42
+ color="transparent"
43
+ mr="10px"
44
+ :size="32"
45
+ />
46
+ <span text="black dark:[#C9D6EF]">{{ title }}</span>
47
+ </div>
48
+ <div class="title-group">
49
+ <span text="black dark:[#C9D6EF]">一体化技术架构 一站式技术支撑</span>
50
+ <div flex="~ row" mt="30px">
51
+ <div
52
+ v-for="(item, index) in designDescription"
53
+ :key="index"
54
+ w-84px
55
+ h-36px
56
+ bg="#2F6BFF"
57
+ rounded-20px
58
+ text="20px #F7F7F7 dark:[#fff]"
59
+ font-normal
60
+ flex="~ center"
61
+ mr="15px"
62
+ >
63
+ {{ item.title }}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <div
70
+ h-full
71
+ bg="[rgba(255,255,255,0.1)] smd:[rgba(255,255,255,1)] dark:[#0F1E3B]"
72
+ backdrop-blur-sm
73
+ w="full smd:1/2 xl:800px 2xl:900px"
74
+ transition-all
75
+ flex="~ col"
76
+ justify-between
77
+ pb="40px"
78
+ relative
79
+ >
80
+ <div
81
+ smd:hidden
82
+ flex="~ row center"
83
+ w-full
84
+ h-200px
85
+ absolute
86
+ disable="none"
87
+ >
88
+ <PubinfoIcon
89
+ name="logo"
90
+ color="transparent"
91
+ :size="42"
92
+ mr-15px
93
+ />
94
+ <span text="33px" font="bold">{{ title }}</span>
95
+ </div>
96
+ <div
97
+ w-full
98
+ flex="~"
99
+ justify-center
100
+ pt="20vh"
101
+ >
102
+ <LoginFrom />
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </template>
107
+
108
+ <style lang="scss" scoped>
109
+ .warp {
110
+ position: relative;
111
+ display: flex;
112
+ flex-direction: row;
113
+ width: 100%;
114
+ height: 100%;
115
+ overflow: hidden;
116
+ background: url("@/assets/images/login-bg.webp") no-repeat;
117
+ background-size: cover;
118
+
119
+ .warp-blank {
120
+ flex: auto;
121
+ flex-basis: 0;
122
+ overflow: hidden;
123
+ }
124
+
125
+ .logo-group {
126
+ display: flex;
127
+ flex-direction: row;
128
+ align-items: center;
129
+ height: 33px;
130
+ margin-bottom: 35px;
131
+ font-size: 24px;
132
+ font-weight: 400;
133
+ color: #151e26;
134
+ }
135
+
136
+ .title-group {
137
+ height: 52px;
138
+ font-size: 38px;
139
+ font-weight: 800;
140
+ color: #072347;
141
+ letter-spacing: 1.5px;
142
+ }
143
+ }
144
+
145
+ @media (width < 1024px) {
146
+ .warp {
147
+ background: url("@/assets/images/login-small.png") no-repeat left;
148
+ background-size: cover;
149
+ }
150
+ }
151
+
152
+ [data-theme="dark"] {
153
+ .warp {
154
+ background: url("@/assets/images/login-bg_dark.webp") no-repeat;
155
+ background-size: cover;
156
+ }
157
+
158
+ @media (width < 1024px) {
159
+ .warp {
160
+ background: url("@/assets/images/login-small_dark.webp") no-repeat left;
161
+ background-size: cover;
162
+ }
163
+ }
164
+ }
165
+ </style>
@@ -0,0 +1,3 @@
1
+ export default {
2
+ extends: ['pubinfo/stylelint'],
3
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "./.pubinfo/tsconfig.app.json"
3
+ }