railway-login-component 1.0.0
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.
- package/.history/.npmrc_20260205164400 +0 -0
- package/.history/.npmrc_20260205164412 +1 -0
- package/.history/.npmrc_20260205164419 +1 -0
- package/.history/.npmrc_20260205164426 +1 -0
- package/.history/.npmrc_20260205164700 +1 -0
- package/.history/.npmrc_20260205164753 +1 -0
- package/README.md +5 -0
- package/package.json +61 -0
- package/postcss.config.js +7 -0
- package/src/assets/background.jpeg +0 -0
- package/src/components/FallingStarsBg.vue +185 -0
- package/src/components/RailwayLogin.vue +305 -0
- package/src/components/Sparkles.vue +152 -0
- package/src/index.ts +11 -0
- package/tailwind.config.js +59 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +30 -0
- package/vite.config.ts +35 -0
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//registry.npmjs.org/:_authToken=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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//registry.npmjs.org/:_authToken=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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//registry.npmjs.org/:_authToken=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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//registry.npmjs.org/:_authToken=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
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Vue 3 + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
|
4
|
+
|
|
5
|
+
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "railway-login-component",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "A modern railway-themed login component for Vue 3",
|
|
7
|
+
"main": "./dist/railway-login-component.umd.js",
|
|
8
|
+
"module": "./dist/railway-login-component.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/railway-login-component.js",
|
|
13
|
+
"require": "./dist/railway-login-component.umd.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "vue-tsc -b && vite build",
|
|
20
|
+
"build:lib": "vue-tsc -b && vite build --mode lib",
|
|
21
|
+
"preview": "vite preview",
|
|
22
|
+
"prepare": "npm run build:lib"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@vueuse/core": "^14.2.0",
|
|
26
|
+
"motion-v": "^1.10.2",
|
|
27
|
+
"sass": "^1.97.3",
|
|
28
|
+
"tw-animate-css": "^1.4.0",
|
|
29
|
+
"vue": "^3.5.24"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vue": "^3.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@inspira-ui/plugins": "^0.0.1",
|
|
36
|
+
"@types/node": "^24.10.1",
|
|
37
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
38
|
+
"@vue/tsconfig": "^0.8.1",
|
|
39
|
+
"autoprefixer": "^10.4.24",
|
|
40
|
+
"class-variance-authority": "^0.7.1",
|
|
41
|
+
"clsx": "^2.1.1",
|
|
42
|
+
"postcss": "^8.5.6",
|
|
43
|
+
"tailwind-merge": "^3.4.0",
|
|
44
|
+
"tailwindcss": "^3.4.0",
|
|
45
|
+
"tailwindcss-animate": "^1.0.7",
|
|
46
|
+
"typescript": "~5.9.3",
|
|
47
|
+
"vite": "^7.2.4",
|
|
48
|
+
"vue-router": "^5.0.2",
|
|
49
|
+
"vue-tsc": "^3.1.4"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"vue",
|
|
53
|
+
"login",
|
|
54
|
+
"railway",
|
|
55
|
+
"component",
|
|
56
|
+
"vue3",
|
|
57
|
+
"tailwindcss"
|
|
58
|
+
],
|
|
59
|
+
"author": "",
|
|
60
|
+
"license": "MIT"
|
|
61
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
3
|
+
|
|
4
|
+
interface Star {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
z: number;
|
|
8
|
+
speed: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
color?: string;
|
|
14
|
+
count?: number;
|
|
15
|
+
class?: string;
|
|
16
|
+
}>(),
|
|
17
|
+
{
|
|
18
|
+
color: "#FFF",
|
|
19
|
+
count: 200,
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const starsCanvas = ref<HTMLCanvasElement | null>(null);
|
|
24
|
+
let perspective: number = 0;
|
|
25
|
+
let stars: Star[] = [];
|
|
26
|
+
let ctx: CanvasRenderingContext2D | null = null;
|
|
27
|
+
let dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
28
|
+
let rafId = 0 as number;
|
|
29
|
+
|
|
30
|
+
// Cache RGB for color — update only when prop changes
|
|
31
|
+
let cachedRgb = hexToRgb(props.color || "#FFF");
|
|
32
|
+
|
|
33
|
+
function hexToRgb(hex: string) {
|
|
34
|
+
let h = (hex || "#000").replace(/^#/, "");
|
|
35
|
+
if (h.length === 3) {
|
|
36
|
+
h = h
|
|
37
|
+
.split("")
|
|
38
|
+
.map((c) => c + c)
|
|
39
|
+
.join("");
|
|
40
|
+
}
|
|
41
|
+
const bigint = Number.parseInt(h, 16) || 0;
|
|
42
|
+
const r = (bigint >> 16) & 255;
|
|
43
|
+
const g = (bigint >> 8) & 255;
|
|
44
|
+
const b = bigint & 255;
|
|
45
|
+
return { r, g, b };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
watch(
|
|
49
|
+
() => props.color,
|
|
50
|
+
(v) => {
|
|
51
|
+
cachedRgb = hexToRgb(v || "#FFF");
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Resize handler uses DPR to set backing store size once
|
|
56
|
+
function resizeCanvas() {
|
|
57
|
+
const canvas = starsCanvas.value;
|
|
58
|
+
if (!canvas) return;
|
|
59
|
+
|
|
60
|
+
dpr = window.devicePixelRatio || 1;
|
|
61
|
+
const width = Math.max(1, Math.floor(canvas.clientWidth));
|
|
62
|
+
const height = Math.max(1, Math.floor(canvas.clientHeight));
|
|
63
|
+
|
|
64
|
+
canvas.width = Math.floor(width * dpr);
|
|
65
|
+
canvas.height = Math.floor(height * dpr);
|
|
66
|
+
canvas.style.width = `${width}px`;
|
|
67
|
+
canvas.style.height = `${height}px`;
|
|
68
|
+
|
|
69
|
+
// Get and set transform so drawing coordinates use CSS pixels
|
|
70
|
+
ctx = canvas.getContext("2d");
|
|
71
|
+
if (ctx) {
|
|
72
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// perspective should be in CSS pixels
|
|
76
|
+
perspective = canvas.width / dpr / 2;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
const canvas = starsCanvas.value;
|
|
81
|
+
if (!canvas) return;
|
|
82
|
+
|
|
83
|
+
window.addEventListener("resize", resizeCanvas, { passive: true });
|
|
84
|
+
resizeCanvas();
|
|
85
|
+
|
|
86
|
+
// Initialize stars using CSS pixel coordinates for x/y and z in CSS pixels
|
|
87
|
+
stars = [];
|
|
88
|
+
const cssWidth = canvas.clientWidth;
|
|
89
|
+
const cssHeight = canvas.clientHeight;
|
|
90
|
+
for (let i = 0; i < props.count!; i++) {
|
|
91
|
+
stars.push({
|
|
92
|
+
x: (Math.random() - 0.5) * 2 * cssWidth,
|
|
93
|
+
y: (Math.random() - 0.5) * 2 * cssHeight,
|
|
94
|
+
z: Math.random() * (cssWidth || 1),
|
|
95
|
+
speed: Math.random() * 5 + 2,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Start loop
|
|
100
|
+
rafId = requestAnimationFrame(loop);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
onBeforeUnmount(() => {
|
|
104
|
+
window.removeEventListener("resize", resizeCanvas);
|
|
105
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Draw star using layered strokes to simulate blur (avoids ctx.shadowBlur)
|
|
109
|
+
function drawStar(star: Star, width: number, height: number) {
|
|
110
|
+
if (!ctx) return;
|
|
111
|
+
|
|
112
|
+
const scale = perspective / (perspective + star.z);
|
|
113
|
+
const x2d = width / 2 + star.x * scale;
|
|
114
|
+
const y2d = height / 2 + star.y * scale;
|
|
115
|
+
const size = Math.max(scale * 3, 0.5);
|
|
116
|
+
|
|
117
|
+
const prevScale = perspective / (perspective + star.z + star.speed * 15);
|
|
118
|
+
const xPrev = width / 2 + star.x * prevScale;
|
|
119
|
+
const yPrev = height / 2 + star.y * prevScale;
|
|
120
|
+
|
|
121
|
+
const rgb = cachedRgb;
|
|
122
|
+
|
|
123
|
+
// Layered strokes from wide+faint to narrow+slightly brighter to fake blur
|
|
124
|
+
const layerAlphas = [0.08, 0.14, 0.22];
|
|
125
|
+
for (let i = 0; i < layerAlphas.length; i++) {
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${layerAlphas[i]})`;
|
|
128
|
+
ctx.lineWidth = size * (1.4 + i * 1.2);
|
|
129
|
+
ctx.moveTo(x2d, y2d);
|
|
130
|
+
ctx.lineTo(xPrev, yPrev);
|
|
131
|
+
ctx.stroke();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Sharp center line
|
|
135
|
+
ctx.beginPath();
|
|
136
|
+
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.6)`;
|
|
137
|
+
ctx.lineWidth = Math.max(1, size);
|
|
138
|
+
ctx.moveTo(x2d, y2d);
|
|
139
|
+
ctx.lineTo(xPrev, yPrev);
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
|
|
142
|
+
// Dot
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`;
|
|
145
|
+
ctx.arc(x2d, y2d, Math.max(0.5, size / 4), 0, Math.PI * 2);
|
|
146
|
+
ctx.fill();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loop() {
|
|
150
|
+
const canvas = starsCanvas.value;
|
|
151
|
+
if (!canvas) return;
|
|
152
|
+
if (!ctx) ctx = canvas.getContext("2d");
|
|
153
|
+
if (!ctx) return;
|
|
154
|
+
|
|
155
|
+
const width = canvas.clientWidth;
|
|
156
|
+
const height = canvas.clientHeight;
|
|
157
|
+
|
|
158
|
+
// Clear using CSS pixel dimensions (context is scaled to DPR)
|
|
159
|
+
ctx.clearRect(0, 0, width, height);
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < stars.length; i++) {
|
|
162
|
+
const star = stars[i] as Star;
|
|
163
|
+
drawStar(star, width, height);
|
|
164
|
+
|
|
165
|
+
star.z -= star.speed;
|
|
166
|
+
|
|
167
|
+
if (star.z <= 0) {
|
|
168
|
+
star.z = width || 1;
|
|
169
|
+
star.x = (Math.random() - 0.5) * 2 * width;
|
|
170
|
+
star.y = (Math.random() - 0.5) * 2 * height;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
rafId = requestAnimationFrame(loop);
|
|
175
|
+
}
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<template>
|
|
179
|
+
<canvas
|
|
180
|
+
ref="starsCanvas"
|
|
181
|
+
class="absolute inset-0 h-full w-full"
|
|
182
|
+
:class="[$props.class]"
|
|
183
|
+
/>
|
|
184
|
+
</template>
|
|
185
|
+
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import FallingStarsBg from "./FallingStarsBg.vue";
|
|
4
|
+
import Sparkles from "./Sparkles.vue";
|
|
5
|
+
|
|
6
|
+
// 定义组件属性
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
// 标题
|
|
9
|
+
title: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: "铁道系统登录"
|
|
12
|
+
},
|
|
13
|
+
// 副标题
|
|
14
|
+
subtitle: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: "请输入您的账号和密码"
|
|
17
|
+
},
|
|
18
|
+
// 背景图片URL
|
|
19
|
+
backgroundImage: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ""
|
|
22
|
+
},
|
|
23
|
+
// 图标(可以是SVG字符串或图片URL)
|
|
24
|
+
icon: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: ""
|
|
27
|
+
},
|
|
28
|
+
// 记住密码默认值
|
|
29
|
+
defaultRememberMe: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
// 登录按钮文本
|
|
34
|
+
loginButtonText: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: "登录"
|
|
37
|
+
},
|
|
38
|
+
// 注册按钮文本
|
|
39
|
+
registerButtonText: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: "立即注册"
|
|
42
|
+
},
|
|
43
|
+
// 忘记密码按钮文本
|
|
44
|
+
forgotPasswordText: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: "忘记密码?"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 定义组件事件
|
|
51
|
+
const emit = defineEmits([
|
|
52
|
+
'login', // 登录事件
|
|
53
|
+
'register', // 注册事件
|
|
54
|
+
'forgot-password' // 忘记密码事件
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
// 表单数据
|
|
58
|
+
const username = ref("");
|
|
59
|
+
const password = ref("");
|
|
60
|
+
const rememberMe = ref(props.defaultRememberMe);
|
|
61
|
+
const isLoading = ref(false);
|
|
62
|
+
const errorMessage = ref("");
|
|
63
|
+
|
|
64
|
+
// 表单验证
|
|
65
|
+
const isFormValid = computed(() => {
|
|
66
|
+
return username.value.trim() !== "" && password.value.trim() !== "";
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 处理登录
|
|
70
|
+
const handleLogin = () => {
|
|
71
|
+
// 验证表单
|
|
72
|
+
if (!isFormValid.value) {
|
|
73
|
+
errorMessage.value = "请输入用户名和密码";
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
errorMessage.value = "";
|
|
78
|
+
isLoading.value = true;
|
|
79
|
+
|
|
80
|
+
// 触发登录事件
|
|
81
|
+
emit('login', {
|
|
82
|
+
username: username.value,
|
|
83
|
+
password: password.value,
|
|
84
|
+
rememberMe: rememberMe.value
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 模拟登录请求(实际使用时,父组件会处理登录逻辑)
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
isLoading.value = false;
|
|
90
|
+
}, 1500);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 处理忘记密码
|
|
94
|
+
const handleForgotPassword = () => {
|
|
95
|
+
emit('forgot-password');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// 处理注册
|
|
99
|
+
const handleRegister = () => {
|
|
100
|
+
emit('register');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// 初始化:检查本地存储中的记住密码
|
|
104
|
+
const initForm = () => {
|
|
105
|
+
const savedUsername = localStorage.getItem('username');
|
|
106
|
+
const savedRememberMe = localStorage.getItem('rememberMe');
|
|
107
|
+
|
|
108
|
+
if (savedRememberMe === 'true' && savedUsername) {
|
|
109
|
+
username.value = savedUsername;
|
|
110
|
+
rememberMe.value = true;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// 初始化表单
|
|
115
|
+
initForm();
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<div class="min-h-screen w-full flex items-center justify-center p-4 overflow-hidden relative">
|
|
120
|
+
<!-- Railway Theme Background Image -->
|
|
121
|
+
<div
|
|
122
|
+
class="absolute inset-0 bg-cover bg-center z-0"
|
|
123
|
+
:style="{ backgroundImage: backgroundImage ? `url('${backgroundImage}')` : `url('https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=modern%20railway%20station%20interior%20with%20blue%20lighting%20and%20futuristic%20design%2C%20blurred%20background%20for%20login%20page&image_size=landscape_16_9')` }"
|
|
124
|
+
></div>
|
|
125
|
+
|
|
126
|
+
<!-- 本地背景图片使用示例:
|
|
127
|
+
<RailwayLogin
|
|
128
|
+
backgroundImage="/src/assets/background.jpg"
|
|
129
|
+
@login="handleLogin"
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
或者使用import引入:
|
|
133
|
+
<script setup>
|
|
134
|
+
import backgroundImage from '@/assets/background.jpg';
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<RailwayLogin :backgroundImage="backgroundImage" @login="handleLogin" />
|
|
139
|
+
</template>
|
|
140
|
+
-->
|
|
141
|
+
|
|
142
|
+
<div class="relative w-full max-w-md">
|
|
143
|
+
<!-- Login Card -->
|
|
144
|
+
<div
|
|
145
|
+
class="relative bg-gradient-to-br from-gray-900/90 to-gray-800/90 backdrop-blur-md rounded-2xl border border-red-500/30 shadow-xl shadow-red-500/10 p-8">
|
|
146
|
+
<!-- Falling Stars Background for Input Window -->
|
|
147
|
+
<div class="absolute inset-0 rounded-2xl overflow-hidden -z-10">
|
|
148
|
+
<FallingStarsBg class="bg-transparent" :color="'#a5b4fc'" :count="25" />
|
|
149
|
+
</div>
|
|
150
|
+
<!-- Logo and Title -->
|
|
151
|
+
<div class="text-center mb-3">
|
|
152
|
+
<div class="w-20 h-20 mx-auto mb-4 rounded-lg bg-gradient-to-br from-red-600 to-red-800 flex items-center justify-center">
|
|
153
|
+
<!-- Custom Icon -->
|
|
154
|
+
<div v-if="icon" class="w-10 h-10 flex items-center justify-center">
|
|
155
|
+
<!-- If icon is an SVG string -->
|
|
156
|
+
<div v-if="icon.startsWith('<svg')" class="w-full h-full" v-html="icon"></div>
|
|
157
|
+
<!-- If icon is an image URL -->
|
|
158
|
+
<img v-else class="w-full h-full object-contain" :src="icon" :alt="title + ' icon'" />
|
|
159
|
+
</div>
|
|
160
|
+
<!-- Default Icon -->
|
|
161
|
+
<svg v-else class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 12"
|
|
162
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
163
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
|
164
|
+
d="M2 6h20M4 4h16M4 8h16M2 6l2-2M20 6l2-2M2 6l2 2M20 6l2 2M8 4v4M16 4v4M10 2h4v8h-4V2z">
|
|
165
|
+
</path>
|
|
166
|
+
</svg>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="flex w-full flex-col items-center justify-center overflow-hidden rounded-md my-4">
|
|
169
|
+
<div class="relative h-14 w-full max-w-md">
|
|
170
|
+
<div
|
|
171
|
+
class="absolute inset-x-20 top-0 h-[2px] w-3/4 bg-gradient-to-r from-transparent via-indigo-500 to-transparent blur-sm" />
|
|
172
|
+
<div
|
|
173
|
+
class="absolute inset-x-20 top-0 h-px w-3/4 bg-gradient-to-r from-transparent via-indigo-500 to-transparent" />
|
|
174
|
+
<div
|
|
175
|
+
class="absolute inset-x-40 top-0 h-[5px] w-1/4 bg-gradient-to-r from-transparent via-sky-500 to-transparent blur-sm" />
|
|
176
|
+
<div
|
|
177
|
+
class="absolute inset-x-40 top-0 h-px w-1/4 bg-gradient-to-r from-transparent via-sky-500 to-transparent" />
|
|
178
|
+
|
|
179
|
+
<Sparkles background="transparent" :min-size="0.3" :max-size="1.2" :particle-density="800"
|
|
180
|
+
class="size-full" :particle-color="'#a5b4fc'" />
|
|
181
|
+
|
|
182
|
+
<div
|
|
183
|
+
class="absolute inset-0 size-full [mask-image:radial-gradient(250px_150px_at_top,transparent_20%,white)]" />
|
|
184
|
+
<!-- Title -->
|
|
185
|
+
<h1
|
|
186
|
+
class="absolute top-2 left-0 right-0 z-20 text-center text-2xl md:text-3xl lg:text-4xl font-bold text-white mb-4">
|
|
187
|
+
{{ title }}
|
|
188
|
+
</h1>
|
|
189
|
+
</div>
|
|
190
|
+
<p class="text-center text-gray-400 z-20 px-4 text-sm">
|
|
191
|
+
{{ subtitle }}
|
|
192
|
+
</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- Error Message -->
|
|
197
|
+
<div v-if="errorMessage" class="mb-4 p-3 bg-red-900/30 border border-red-500/50 rounded-lg text-red-400 text-sm">
|
|
198
|
+
{{ errorMessage }}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<!-- Login Form -->
|
|
202
|
+
<form @submit.prevent="handleLogin" class="space-y-6">
|
|
203
|
+
<!-- Username Input -->
|
|
204
|
+
<div class="space-y-2">
|
|
205
|
+
<label for="username" class="block text-sm font-medium text-gray-300">用户名</label>
|
|
206
|
+
<div class="relative">
|
|
207
|
+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
208
|
+
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
|
209
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
210
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
211
|
+
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
212
|
+
</svg>
|
|
213
|
+
</div>
|
|
214
|
+
<input id="username" v-model="username" type="text"
|
|
215
|
+
class="block w-full pl-10 pr-3 py-3 bg-gray-800/50 border border-gray-700 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 text-white placeholder-gray-500 transition-all"
|
|
216
|
+
placeholder="请输入用户名"
|
|
217
|
+
autocomplete="username" />
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<!-- Password Input -->
|
|
222
|
+
<div class="space-y-2">
|
|
223
|
+
<div class="flex items-center justify-between">
|
|
224
|
+
<label for="password" class="block text-sm font-medium text-gray-300">密码</label>
|
|
225
|
+
<a href="#" @click.prevent="handleForgotPassword" class="text-sm text-red-400 hover:text-red-300 transition-colors">{{ forgotPasswordText }}</a>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="relative">
|
|
228
|
+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
229
|
+
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
|
230
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
231
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
232
|
+
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z">
|
|
233
|
+
</path>
|
|
234
|
+
</svg>
|
|
235
|
+
</div>
|
|
236
|
+
<input id="password" v-model="password" type="password"
|
|
237
|
+
class="block w-full pl-10 pr-3 py-3 bg-gray-800/50 border border-gray-700 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 text-white placeholder-gray-500 transition-all"
|
|
238
|
+
placeholder="请输入密码"
|
|
239
|
+
autocomplete="current-password" />
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<!-- Remember Me -->
|
|
244
|
+
<div class="flex items-center">
|
|
245
|
+
<input id="remember-me" v-model="rememberMe" type="checkbox"
|
|
246
|
+
class="h-4 w-4 text-red-600 focus:ring-red-500 border-gray-600 rounded" />
|
|
247
|
+
<label for="remember-me" class="ml-2 block text-sm text-gray-300">记住我</label>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Login Button -->
|
|
251
|
+
<button type="submit" :disabled="isLoading || !isFormValid"
|
|
252
|
+
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-red-600 to-red-800 hover:from-red-700 hover:to-red-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-60 disabled:cursor-not-allowed transition-all">
|
|
253
|
+
<span v-if="isLoading" class="flex items-center">
|
|
254
|
+
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg"
|
|
255
|
+
fill="none" viewBox="0 0 24 24">
|
|
256
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
|
|
257
|
+
stroke-width="4"></circle>
|
|
258
|
+
<path class="opacity-75" fill="currentColor"
|
|
259
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
|
260
|
+
</path>
|
|
261
|
+
</svg>
|
|
262
|
+
登录中...
|
|
263
|
+
</span>
|
|
264
|
+
<span v-else>{{ loginButtonText }}</span>
|
|
265
|
+
</button>
|
|
266
|
+
|
|
267
|
+
<!-- Sign Up Link -->
|
|
268
|
+
<div class="text-center mt-6">
|
|
269
|
+
<p class="text-sm text-gray-400">
|
|
270
|
+
还没有账号?
|
|
271
|
+
<a href="#" @click.prevent="handleRegister" class="font-medium text-red-400 hover:text-red-300 transition-colors">{{ registerButtonText }}</a>
|
|
272
|
+
</p>
|
|
273
|
+
</div>
|
|
274
|
+
</form>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<!-- Railway Decoration -->
|
|
278
|
+
<div
|
|
279
|
+
class="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-transparent via-red-500 to-transparent">
|
|
280
|
+
</div>
|
|
281
|
+
<div class="absolute bottom-1 left-0 right-0 flex justify-between px-4">
|
|
282
|
+
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
|
283
|
+
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
|
284
|
+
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
|
285
|
+
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
|
286
|
+
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</template>
|
|
291
|
+
|
|
292
|
+
<style scoped>
|
|
293
|
+
/* Additional custom styles can be added here */
|
|
294
|
+
|
|
295
|
+
/* Override browser autofill styles */
|
|
296
|
+
:deep(input:-webkit-autofill),
|
|
297
|
+
:deep(input:-webkit-autofill:hover),
|
|
298
|
+
:deep(input:-webkit-autofill:focus),
|
|
299
|
+
:deep(input:-webkit-autofill:active) {
|
|
300
|
+
-webkit-background-clip: text;
|
|
301
|
+
-webkit-text-fill-color: white;
|
|
302
|
+
background-color: rgba(31, 41, 55, 0.5) !important;
|
|
303
|
+
transition: background-color 5000s ease-in-out 0s;
|
|
304
|
+
}
|
|
305
|
+
</style>
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRafFn } from "@vueuse/core";
|
|
3
|
+
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
background?: string;
|
|
7
|
+
particleColor?: string;
|
|
8
|
+
minSize?: number;
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
speed?: number;
|
|
11
|
+
particleDensity?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Particle {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
size: number;
|
|
18
|
+
opacity: number;
|
|
19
|
+
vx: number;
|
|
20
|
+
vy: number;
|
|
21
|
+
phase: number;
|
|
22
|
+
phaseSpeed: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
background: "#0d47a1",
|
|
27
|
+
particleColor: "#ffffff",
|
|
28
|
+
minSize: 1,
|
|
29
|
+
maxSize: 3,
|
|
30
|
+
speed: 4,
|
|
31
|
+
particleDensity: 120,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const containerRef = useTemplateRef("containerRef");
|
|
35
|
+
const canvasRef = useTemplateRef("canvasRef");
|
|
36
|
+
const particles = ref<Particle[]>([]);
|
|
37
|
+
const ctx = ref<CanvasRenderingContext2D | null>(null);
|
|
38
|
+
|
|
39
|
+
// Adjust canvas size on mount and resize
|
|
40
|
+
function resizeCanvas() {
|
|
41
|
+
if (!canvasRef.value || !containerRef.value) return;
|
|
42
|
+
|
|
43
|
+
const dpr = window.devicePixelRatio || 1;
|
|
44
|
+
const rect = containerRef.value.getBoundingClientRect();
|
|
45
|
+
|
|
46
|
+
canvasRef.value.width = rect.width * dpr;
|
|
47
|
+
canvasRef.value.height = rect.height * dpr;
|
|
48
|
+
|
|
49
|
+
if (ctx.value) {
|
|
50
|
+
ctx.value.scale(dpr, dpr);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function generateParticles(): void {
|
|
55
|
+
const newParticles: Particle[] = [];
|
|
56
|
+
const count = props.particleDensity;
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < count; i++) {
|
|
59
|
+
const baseSpeed = 0.05;
|
|
60
|
+
const speedVariance = Math.random() * 0.3 + 0.7;
|
|
61
|
+
|
|
62
|
+
newParticles.push({
|
|
63
|
+
x: Math.random() * 100,
|
|
64
|
+
y: Math.random() * 100,
|
|
65
|
+
size: Math.random() * (props.maxSize - props.minSize) + props.minSize,
|
|
66
|
+
opacity: Math.random() * 0.5 + 0.3,
|
|
67
|
+
vx: (Math.random() - 0.5) * baseSpeed * speedVariance * props.speed,
|
|
68
|
+
vy: ((Math.random() - 0.5) * baseSpeed - baseSpeed * 0.3) * speedVariance * props.speed,
|
|
69
|
+
phase: Math.random() * Math.PI * 2,
|
|
70
|
+
phaseSpeed: 0.015,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
particles.value = newParticles;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function updateAndDrawParticles() {
|
|
78
|
+
if (!ctx.value || !canvasRef.value) return;
|
|
79
|
+
|
|
80
|
+
const canvas = canvasRef.value;
|
|
81
|
+
ctx.value.clearRect(0, 0, canvas.width, canvas.height);
|
|
82
|
+
|
|
83
|
+
particles.value = particles.value.map((particle) => {
|
|
84
|
+
let newX = particle.x + particle.vx;
|
|
85
|
+
let newY = particle.y + particle.vy;
|
|
86
|
+
|
|
87
|
+
if (newX < -2) newX = 102;
|
|
88
|
+
if (newX > 102) newX = -2;
|
|
89
|
+
if (newY < -2) newY = 102;
|
|
90
|
+
if (newY > 102) newY = -2;
|
|
91
|
+
|
|
92
|
+
const newPhase = (particle.phase + particle.phaseSpeed) % (Math.PI * 2);
|
|
93
|
+
const opacity = 0.3 + (Math.sin(newPhase) * 0.3 + 0.3);
|
|
94
|
+
|
|
95
|
+
// Draw particle
|
|
96
|
+
ctx.value!.beginPath();
|
|
97
|
+
ctx.value!.arc(
|
|
98
|
+
(newX * canvas.width) / 100,
|
|
99
|
+
(newY * canvas.height) / 100,
|
|
100
|
+
particle.size,
|
|
101
|
+
0,
|
|
102
|
+
Math.PI * 2,
|
|
103
|
+
);
|
|
104
|
+
ctx.value!.fillStyle = `${props.particleColor}${Math.floor(opacity * 255)
|
|
105
|
+
.toString(16)
|
|
106
|
+
.padStart(2, "0")}`;
|
|
107
|
+
ctx.value!.fill();
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...particle,
|
|
111
|
+
x: newX,
|
|
112
|
+
y: newY,
|
|
113
|
+
phase: newPhase,
|
|
114
|
+
opacity,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { pause, resume } = useRafFn(updateAndDrawParticles, { immediate: false });
|
|
120
|
+
|
|
121
|
+
// Handle window resize
|
|
122
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
123
|
+
|
|
124
|
+
onMounted(() => {
|
|
125
|
+
if (!canvasRef.value) return;
|
|
126
|
+
|
|
127
|
+
ctx.value = canvasRef.value.getContext("2d");
|
|
128
|
+
resizeCanvas();
|
|
129
|
+
generateParticles();
|
|
130
|
+
|
|
131
|
+
// Set up resize observer
|
|
132
|
+
resizeObserver = new ResizeObserver(resizeCanvas);
|
|
133
|
+
if (containerRef.value) {
|
|
134
|
+
resizeObserver.observe(containerRef.value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
resume();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
onBeforeUnmount(() => {
|
|
141
|
+
pause();
|
|
142
|
+
if (resizeObserver && containerRef.value) {
|
|
143
|
+
resizeObserver.unobserve(containerRef.value);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<template>
|
|
149
|
+
<div ref="containerRef" class="relative size-full overflow-hidden will-change-transform" :style="{ background }">
|
|
150
|
+
<canvas ref="canvasRef" class="absolute inset-0 size-full" />
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import RailwayLogin from './components/RailwayLogin.vue';
|
|
2
|
+
import FallingStarsBg from './components/FallingStarsBg.vue';
|
|
3
|
+
import Sparkles from './components/Sparkles.vue';
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
RailwayLogin,
|
|
7
|
+
FallingStarsBg,
|
|
8
|
+
Sparkles
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default RailwayLogin;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// tailwind.config.js
|
|
2
|
+
/** @type {import('tailwindcss').Config} */
|
|
3
|
+
import { setupInspiraUI } from "@inspira-ui/plugins";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
darkMode: "selector",
|
|
7
|
+
content: [
|
|
8
|
+
"./index.html",
|
|
9
|
+
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
|
10
|
+
],
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {
|
|
13
|
+
colors: {
|
|
14
|
+
border: "hsl(var(--border))",
|
|
15
|
+
input: "hsl(var(--input))",
|
|
16
|
+
ring: "hsl(var(--ring))",
|
|
17
|
+
background: "hsl(var(--background))",
|
|
18
|
+
foreground: "hsl(var(--foreground))",
|
|
19
|
+
primary: {
|
|
20
|
+
DEFAULT: "hsl(var(--primary))",
|
|
21
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
22
|
+
},
|
|
23
|
+
secondary: {
|
|
24
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
25
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
26
|
+
},
|
|
27
|
+
destructive: {
|
|
28
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
29
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
30
|
+
},
|
|
31
|
+
muted: {
|
|
32
|
+
DEFAULT: "hsl(var(--muted))",
|
|
33
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
34
|
+
},
|
|
35
|
+
accent: {
|
|
36
|
+
DEFAULT: "hsl(var(--accent))",
|
|
37
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
38
|
+
},
|
|
39
|
+
popover: {
|
|
40
|
+
DEFAULT: "hsl(var(--popover))",
|
|
41
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
42
|
+
},
|
|
43
|
+
card: {
|
|
44
|
+
DEFAULT: "hsl(var(--card))",
|
|
45
|
+
foreground: "hsl(var(--card-foreground))",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
borderRadius: {
|
|
49
|
+
lg: "var(--radius)",
|
|
50
|
+
md: "calc(var(--radius) - 2px)",
|
|
51
|
+
sm: "calc(var(--radius) - 4px)",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
plugins: [
|
|
56
|
+
require("tailwindcss-animate"),
|
|
57
|
+
setupInspiraUI,
|
|
58
|
+
],
|
|
59
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
|
+
"types": [
|
|
6
|
+
"vite/client"
|
|
7
|
+
],
|
|
8
|
+
/* Linting */
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"erasableSyntaxOnly": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"noUncheckedSideEffectImports": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": [
|
|
18
|
+
"src/*"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.ts",
|
|
24
|
+
"src/**/*.tsx",
|
|
25
|
+
"src/**/*.vue"
|
|
26
|
+
]
|
|
27
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true,
|
|
24
|
+
"baseUrl": ".",
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": ["src/*"]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"include": ["vite.config.ts"]
|
|
30
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
export default defineConfig(({ mode }) => {
|
|
6
|
+
if (mode === 'lib') {
|
|
7
|
+
return {
|
|
8
|
+
plugins: [vue()],
|
|
9
|
+
build: {
|
|
10
|
+
lib: {
|
|
11
|
+
entry: resolve(__dirname, 'src/index.ts'),
|
|
12
|
+
name: 'RailwayLogin',
|
|
13
|
+
fileName: (format) => `railway-login-component.${format}.js`,
|
|
14
|
+
},
|
|
15
|
+
rollupOptions: {
|
|
16
|
+
external: ['vue'],
|
|
17
|
+
output: {
|
|
18
|
+
globals: {
|
|
19
|
+
vue: 'Vue',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
plugins: [vue()],
|
|
29
|
+
resolve: {
|
|
30
|
+
alias: {
|
|
31
|
+
'@': resolve(__dirname, 'src'),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
})
|