1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env bash
set -e

APP_NAME="vue3-mobile-app"

echo "📁 Creating project folder..."
pnpm create vite "$APP_NAME" --template vue-ts
cd "$APP_NAME"

cat > packages.json << 'EOF'

EOF

echo "🔧 Installing dependencies..."
pnpm add vue-router@4 @vueuse/core pinia pinia-plugin-persistedstate vue-i18n@9 axios vant
pnpm add -D @tailwindcss/vite @vitejs/plugin-vue \
@intlify/unplugin-vue-i18n unplugin-auto-import unplugin-vue-components \
@vant/auto-import-resolver vite-tsconfig-paths vue-tsc \
eslint prettier stylelint husky lint-staged typescript

echo "📦 Setting up Vite config..."
cat > vite.config.ts << 'EOF'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import VueI18n from "@intlify/unplugin-vue-i18n/vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [
vue(),
tsconfigPaths(), // 同步 tsconfig.json 别名,避免在 vite.config 重复设置 :contentReference[oaicite:1]{index=1}
VueI18n({ include: path.resolve(__dirname, "src/i18n/locales/**") }),
AutoImport({
imports: [
"vue",
"vue-router",
"pinia",
"vue-i18n",
"@vueuse/core",
{
axios: [
["default", "axios"], // Automatically import axios as `axios`
],
},
],
dts: "src/auto-imports.d.ts",
vueTemplate: true, // 模板中允许直接使用 API 无需手动导入 :contentReference[oaicite:2]{index=2}
dirs: ["src/hooks", "src/composables"], // 自动导入你的 reusable 函数
eslintrc: {
enabled: true,
filepath: "./.eslintrc-auto-import.json",
globalsPropValue: true,
},
}),
Components({
dts: "src/components.d.ts",
resolvers: [VantResolver()],
}),
tailwindcss(),
],
resolve: {
alias: { "@": path.resolve(__dirname, "./src") },
},
});
EOF

echo "🗂️ Creating folders & example files..."
mkdir -p src/{router,store,api,i18n/locales}
touch src/assets/main.css

cat > src/i18n/locales/en.json << 'EOF'
{ "hello": "Hello", "welcome": "Welcome to the app!" }
EOF

cat > src/i18n/index.ts << 'EOF'
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
export const i18n = createI18n({ legacy: false, locale: 'en', fallbackLocale: 'en', messages: { en } })
EOF

cat > src/router/index.ts << 'EOF'
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

const routes = [{ path: '/', name: 'Home', component: Home }]
export const router = createRouter({ history: createWebHistory(), routes })
EOF

cat > src/store/user.ts << 'EOF'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ token: '' }),
persist: true,
actions: {
setToken(token: string) {
this.token = token
},
},
})
EOF

cat > src/api/index.ts << 'EOF'
import axios from 'axios'
export const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE || '/', timeout: 10000 })
api.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = \`Bearer \${token}\`
return config
})
EOF

cat > src/assets/main.css << 'EOF'
@import "tailwindcss";

/* 全局基础样式 */
@layer base {
body { @apply bg-gray-50 text-gray-900; }
}

/* 自定义组件样式 */
@layer components {
.btn-primary {
@apply px-4 py-2 bg-indigo-600 text-white rounded;
}
}

/* 自定义工具类 */
@layer utilities {
.no-scrollbar {
@apply scrollbar-hide;
}
}

/* 可选:CSS-first 主题覆盖 */
@theme inline {
--color-primary: #5c6ac4;
}
EOF

rm src/style.css

mkdir -p src/views

cat > src/App.vue << 'EOF'
<template>
<router-view />
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
if (!userStore.token) {
userStore.setToken('example-token') // Set a default token for demonstration
}
</script>
EOF

cat > src/views/Home.vue << 'EOF'
<template>
<div class="p-4">
<h1 class="text-xl font-bold">{{ $t('hello') }}</h1>
<p>{{ $t('welcome') }}</p>
<van-button type="primary" @click="click">{{ $t('welcome') }}</van-button>
</div>
</template>
<script setup lang="ts">
const click = () => console.log('Clicked!')
</script>
EOF

cat > src/main.ts << 'EOF'
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persistedstate'
import { i18n } from '@/i18n'
import { router } from '@/router'
import '@/assets/main.css'

const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersist)

app.use(pinia).use(router).use(i18n).mount('#app')
EOF

echo "🔧 Setting up husky and lint-staged..."
git init
pnpm dlx husky-init
pnpm install
pnpm exec husky add .husky/pre-commit "npx lint-staged"

cat > .lintstagedrc.json << 'EOF'
{
"*.ts": ["eslint --fix"],
"*.vue": ["eslint --fix"],
"*.{css,scss}": ["stylelint --fix"]
}
EOF

echo "✅ DONE! To start:"
echo "cd $APP_NAME && pnpm dev"