├── .env
├── .env.dev
├── .env.prod
├── .eslintrc.cjs
├── .github
└── workflows
│ ├── codeql.yml
│ ├── nuxtjs.yml
│ └── vercel.yml
├── .gitignore
├── .husky
└── commit-msg
├── .storybook
├── main.js
├── preview-head.html
├── preview.js
└── tailwind.css
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh-CN.md
├── app.vue
├── assets
└── css
│ └── tailwind.css
├── commitlint.config.js
├── components
├── App
│ ├── Footer.vue
│ ├── Header.vue
│ └── LangSwitcher.vue
├── Button
│ ├── Button.stories.ts
│ └── Button.vue
├── Contact
│ └── Form.vue
├── Dashboard
│ ├── Header.vue
│ ├── Sidebar.vue
│ ├── SidebarItem.vue
│ ├── SidebarLink.vue
│ └── Stats
│ │ └── Item.vue
├── Form
│ ├── Input.vue
│ └── Switch.vue
├── HeroSection
│ └── HeroSection.vue
├── Home
│ ├── About.vue
│ ├── Features.vue
│ └── Quote.vue
├── PageHeader
│ └── PageHeader.vue
├── SectionHeader.vue
├── UI
│ ├── Autocomplete
│ │ ├── Autocomplete.stories.ts
│ │ └── Autocomplete.vue
│ ├── Card
│ │ ├── Card.vue
│ │ ├── CardBody.vue
│ │ ├── CardFooter.vue
│ │ └── CardHeader.vue
│ ├── Collapsible
│ │ ├── CollapseTransition.vue
│ │ ├── Collapsible.stories.ts
│ │ ├── Collapsible.vue
│ │ ├── CollapsibleGroup.stories.ts
│ │ └── CollapsibleGroup.vue
│ ├── Dropdown
│ │ ├── Dropdown.stories.ts
│ │ ├── Dropdown.vue
│ │ ├── DropdownButton.vue
│ │ ├── DropdownItem.vue
│ │ └── types.d.ts
│ ├── Modal
│ │ ├── Backdrop.vue
│ │ └── Modal.vue
│ ├── NavDrawer
│ │ └── NavDrawer.vue
│ └── Select
│ │ ├── Select.stories.ts
│ │ ├── Select.vue
│ │ └── types.d.ts
└── blog
│ ├── PostImage.vue
│ └── PostItem.vue
├── composables
├── auth.ts
├── states.ts
└── strapi.ts
├── content
├── articles
│ ├── base.md
│ ├── index.md
│ └── nuxt-template.md
└── wind
│ └── index.md
├── layouts
├── auth.vue
├── dashboard.vue
└── default.vue
├── middleware
├── auth.ts
└── guest.ts
├── nuxt.config.ts
├── package.json
├── pages
├── 401.vue
├── 403.vue
├── auth
│ ├── forgot-password.vue
│ ├── login.vue
│ ├── register.vue
│ └── reset-password.vue
├── blog
│ └── index.vue
├── counter.vue
├── dashboard
│ └── index.vue
├── forms.vue
├── index.vue
├── playground
│ └── index.vue
├── store
│ ├── [slug].vue
│ └── index.vue
└── swiper.vue
├── plugins
└── gtag.client.ts
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier.config.js
├── public
└── images
│ └── login-illustration.svg
├── server
└── api
│ ├── auth
│ └── login.post.ts
│ ├── home.get.ts
│ └── store
│ └── products.get.ts
├── stores
├── auth.ts
└── counter.ts
├── stub
└── types__react
│ ├── index.d.ts
│ └── package.json
├── tailwind.config.js
├── tsconfig.json
└── types
└── index.ts
/.env:
--------------------------------------------------------------------------------
1 | STRAPI_URL=https://bapi.warsono.id
2 | NUXT_PUBLIC_GA_ID=G-0XXRF7JV2H
--------------------------------------------------------------------------------
/.env.dev:
--------------------------------------------------------------------------------
1 | STRAPI_URL=http://localhost:1337
2 | NUXT_PUBLIC_GA_ID=G-VALUE
--------------------------------------------------------------------------------
/.env.prod:
--------------------------------------------------------------------------------
1 | STRAPI_URL=http://localhost:1337
2 | NUXT_PUBLIC_GA_ID=G-VALUE
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: '@antfu',
3 | rules: {
4 | 'vue/custom-event-name-casing': [
5 | 'off',
6 | 'camelCase' || 'kebab-case',
7 | {
8 | ignores: [],
9 | },
10 | ],
11 | 'vue/v-on-event-hyphenation': [
12 | 'off', {
13 | autofix: true,
14 | ignore: [],
15 | },
16 | ],
17 | 'no-console': 'off',
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '45 23 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 | with:
74 | category: "/language:${{matrix.language}}"
75 |
--------------------------------------------------------------------------------
/.github/workflows/nuxtjs.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a Nuxt site to GitHub Pages
2 | #
3 | # To get started with Nuxt see: https://nuxtjs.org/docs/get-started/installation
4 | #
5 | name: Deploy Nuxt site to Pages
6 |
7 | on:
8 | # Runs on pushes targeting the default branch
9 | push:
10 | branches: [main]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | # workflow_dispatch:
14 |
15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
16 | permissions:
17 | contents: read
18 | pages: write
19 | id-token: write
20 |
21 | # Allow one concurrent deployment
22 | concurrency:
23 | group: pages
24 | cancel-in-progress: true
25 |
26 | jobs:
27 | # Build job
28 | build:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v3
33 |
34 | - name: Detect package manager
35 | id: detect-package-manager
36 | run: |
37 | if [ -f "${{ github.workspace }}/package.json" ]; then
38 | echo "manager=npm" >> $GITHUB_OUTPUT
39 | echo "command=install" >> $GITHUB_OUTPUT
40 | exit 0
41 | elif [ -f "${{ github.workspace }}/package.json" ]; then
42 | echo "manager=npm" >> $GITHUB_OUTPUT
43 | echo "command=ci" >> $GITHUB_OUTPUT
44 | exit 0
45 | else
46 | echo "Unable to determine packager manager"
47 | exit 1
48 | fi
49 | - name: Setup Node
50 | uses: actions/setup-node@v3
51 | with:
52 | node-version: '18'
53 | cache: ${{ steps.detect-package-manager.outputs.manager }}
54 | - name: Setup Pages
55 | uses: actions/configure-pages@v2
56 | with:
57 | # Automatically inject router.base in your Nuxt configuration file and set
58 | # target to static (https://nuxtjs.org/docs/configuration-glossary/configuration-target/).
59 | #
60 | # You may remove this line if you want to manage the configuration yourself.
61 | static_site_generator: nuxt
62 | - name: Restore cache
63 | uses: actions/cache@v3
64 | with:
65 | path: |
66 | dist
67 | .nuxt
68 | key: ${{ runner.os }}-nuxt-build-${{ hashFiles('dist') }}
69 | restore-keys: |
70 | ${{ runner.os }}-nuxt-build-
71 | - name: Install dependencies
72 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
73 | - name: Static HTML export with Nuxt
74 | run: ${{ steps.detect-package-manager.outputs.manager }} run generate
75 | - name: Upload artifact
76 | uses: actions/upload-pages-artifact@v1
77 | with:
78 | path: ./dist
79 |
80 | # Deployment job
81 | deploy:
82 | environment:
83 | name: github-pages
84 | url: ${{ steps.deployment.outputs.page_url }}
85 | runs-on: ubuntu-latest
86 | needs: build
87 | steps:
88 | - name: Deploy to GitHub Pages
89 | id: deployment
90 | uses: actions/deploy-pages@v1
91 |
--------------------------------------------------------------------------------
/.github/workflows/vercel.yml:
--------------------------------------------------------------------------------
1 | name: deploy website to vercel
2 | on:
3 | # Runs on pushes targeting the default branch
4 | push:
5 | branches: [main]
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | # your build commands
12 | # - run: |
13 | # ng build --prod
14 | - uses: amondnet/vercel-action@v20 # deploy
15 | with:
16 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
17 | github-token: ${{ secrets.GITHUB_TOKEN }} # Optional
18 | vercel-args: --prod # Optional
19 | vercel-org-id: ${{ secrets.ORG_ID}} # Required
20 | vercel-project-id: ${{ secrets.PROJECT_ID}} # Required
21 | working-directory: .
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 |
4 | nuxt.d.ts
5 | .output
6 | .DS_Store### Node template
7 | # Logs
8 | logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # Snowpack dependency directory (https://snowpack.dev/)
50 | web_modules/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variables file
77 | .env
78 | .env.test
79 |
80 | # parcel-bundler cache (https://parceljs.org/)
81 | .cache
82 | .parcel-cache
83 |
84 | # Next.js build output
85 | .next
86 | out
87 |
88 | # Nuxt.js build / generate output
89 | .nuxt
90 | dist
91 |
92 | # Gatsby files
93 | .cache/
94 | # Comment in the public line in if your project uses Gatsby and not Next.js
95 | # https://nextjs.org/blog/next-9-1#public-directory-support
96 | # public
97 |
98 | # vuepress build output
99 | .vuepress/dist
100 |
101 | # Serverless directories
102 | .serverless/
103 |
104 | # FuseBox cache
105 | .fusebox/
106 |
107 | # DynamoDB Local files
108 | .dynamodb/
109 |
110 | # TernJS port file
111 | .tern-port
112 |
113 | # Stores VSCode versions used for testing VSCode extensions
114 | .vscode-test
115 |
116 | # yarn v2
117 | .yarn/cache
118 | .yarn/unplugged
119 | .yarn/build-state.yml
120 | .yarn/install-state.gz
121 | .pnp.*
122 |
123 | .idea/
124 | .vercel
125 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit "${1}"
5 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const { mergeConfig } = require('vite');
2 | const Icons = require('unplugin-icons/vite');
3 |
4 | module.exports = {
5 | stories: [
6 | '../components/**/*.stories.mdx',
7 | '../components/**/*.stories.@(js|jsx|ts|tsx)',
8 | ],
9 | addons: [
10 | '@storybook/addon-links',
11 | '@storybook/addon-essentials',
12 | '@storybook/addon-interactions',
13 | ],
14 | framework: '@storybook/vue3',
15 | core: {
16 | builder: '@storybook/builder-vite',
17 | },
18 | features: {
19 | storyStoreV7: true,
20 | },
21 | async viteFinal(config, { configType }) {
22 | return mergeConfig(config, {
23 | plugins: [
24 | Icons({
25 | /* options */
26 | }),
27 | ],
28 | });
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import './tailwind.css';
2 |
3 | export const parameters = {
4 | actions: { argTypesRegex: '^on[A-Z].*' },
5 | controls: {
6 | matchers: {
7 | color: /(background|color)$/i,
8 | date: /Date$/,
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/.storybook/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.enable": false,
3 | "editor.formatOnSave": false,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": true
6 | },
7 | "i18n-ally.localesPaths": [
8 | "locales"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # (2022-12-01)
2 |
3 |
4 | ### Features
5 |
6 | * **添加vecel部署:** 添加一键从本地部署到vecel功能 ([187b3f1](https://github.com/Createitv/nuxt3-tailwind-start/commit/187b3f17a277a730e0f33f1aba79de0d6c371afb))
7 | * **template模板渲染修改:** 修改Nuxt3模板 提交commitlint-cb ([7dfe1f4](https://github.com/Createitv/nuxt3-tailwind-start/commit/7dfe1f41d91ba78711d1359b9de36eef101f0ba5))
8 |
9 |
10 | ### BREAKING CHANGES
11 |
12 | * **添加vecel部署:** deploy to cercel
13 |
14 |
15 |
16 | # (2022-12-01)
17 |
18 |
19 | ### Features
20 |
21 | * **template模板渲染修改:** 修改Nuxt3模板 提交commitlint-cb ([7dfe1f4](https://github.com/Createitv/nuxt3-tailwind-start/commit/7dfe1f41d91ba78711d1359b9de36eef101f0ba5))
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 everyone.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Nuxt 3 Tailwind Starter [中文文档](./README.zh-CN.md)
3 | []()
4 | []()
5 | []()
6 | []()
7 | []()
8 | []()
9 | []()
10 | []()
11 | []()
12 | []()
13 |
14 | Zero Config Nuxt 3 + Tailwind Starter
15 | ## Features
16 | - [Nuxt 3](https://v3.nuxtjs.org/)
17 | - [Nuxt Content v2](https://content.nuxtjs.org/)
18 | - [Tailwind CSS](https://tailwindcss.com/)
19 | - [Nuxt Icon](https://github.com/nuxt-modules/icon)
20 | - State management with [Pinia](https://pinia.vuejs.org/)
21 | - Easy form validation with [vee-validate](https://vee-validate.logaretm.com/v4/)
22 | - Custom authentication store via [`useAuthStore`](./stores/auth.ts)
23 | - Internalization via [@nuxtjs/i18n](https://v8.i18n.nuxtjs.org/)
24 | - `Tailwind CSS` Auto prettier
25 | - Comitlint and auto generate CHANGELOG.md
26 | - Auto push to [`vercel`](https://vercel.com/) `Github Page`on main branch
27 | - Devops Easy
28 | ## Try it Now
29 |
30 | ### Stackblitz
31 |
32 | - Try on [stackblitz](https://stackblitz.com/github/Createitv/nuxt3-tailwind-starter/tree/main)
33 |
34 | ### Online Demo
35 |
36 | - Try [online demo](https://nuxt3-tailwind-start.vercel.app/)
37 |
38 | ### GitHub Template
39 |
40 | [Create a repo from this template on GitHub.](https://github.com/Createitv/nuxt3-tailwind-starter/generate)
41 |
42 | ### Local Install
43 |
44 | If you prefer to do it manually with the cleaner git history
45 |
46 | ```bash
47 | git clone https://github.com/Createitv/nuxt3-tailwind-starter my-app
48 | cd my-app
49 | pnpm install --shamefully-hoist
50 | # or
51 | npm install
52 | ```
53 |
54 | ## Development
55 |
56 | We recommend to look at the [documentation](https://v3.nuxtjs.org).
57 |
58 | Make sure to install the dependencies
59 |
60 | ```bash
61 | npm run dev
62 | ```
63 |
64 | Start the development server on http://localhost:3000
65 |
66 | ## Production
67 |
68 | Build the application for production:
69 |
70 | ```bash
71 | npm run build
72 | ```
73 |
74 | Checkout the [deployment documentation](https://v3.nuxtjs.org/docs/deployment).
75 |
76 | ### Thanks
77 |
78 | [Warsono](https://github.com/gravitano/nuxt3-tailwind-kit)
79 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | # Nuxt 3 Tailwind Starter
2 |
3 | []()
4 | []()
5 | []()
6 | []()
7 | []()
8 | []()
9 | []()
10 | []()
11 | []()
12 | []()
13 |
14 | Zero Config Nuxt 3 + Tailwind Starter
15 |
16 | ### 在线编辑
17 |
18 | - Try on [stackblitz](https://stackblitz.com/github/Createitv/nuxt3-tailwind-starter/tree/main)
19 |
20 | ### 在线演示
21 |
22 | - Try [online demo](https://nuxt3-tailwind-start.vercel.app/)
23 |
24 | ### 模板
25 |
26 | [直接使用此模板](https://github.com/Createitv/nuxt3-tailwind-starter/generate)
27 |
28 | ## 功能支持
29 |
30 | - [Nuxt 3](https://v3.nuxtjs.org/)
31 | - [Nuxt Content v2](https://content.nuxtjs.org/)
32 | - [Tailwind CSS](https://tailwindcss.com/)
33 | - [Nuxt Icon](https://nuxt.com/modules/icon)
34 | - [Pinia](https://pinia.vuejs.org/)
35 | - [vee-validate](https://vee-validate.logaretm.com/v4/)
36 | - `useAuthStore`登录验证
37 | - [@nuxtjs/i18n](https://v8.i18n.nuxtjs.org/)
38 |
39 | ### 其他
40 | - 🎨 使用Tailwind作为CSS组件库
41 | - 💪 Eslint和Stylelint代码检验;
42 | - 🐶 创建Git commit message cn规范校验;
43 | - 🎉 天然支持Vue3、Typescript、Vite等;
44 | - 🍍 集成Pinia作为状态管理;
45 | - 🥤 集成vueuse作为Hooks库;
46 | - 🎊文件式路由、componentsAPI自动导入、组件自动导入等;
47 | - 🎁 集成Nuxt Content作为Markdown文件管理;
48 | - 🎁 集成Nuxt Icon作为SVG图标管理;
49 | - 🎁 集成Nuxt I18n作为国际化管理;
50 | - 🎁 集成Vee-Validate作为表单验证;
51 | - 🎁 集成Vercel作为自动部署;
52 | - 🎁 集成Pnpm作为包管理工具;
53 | - 🎁 集成Github Action作为CI/CD;
54 | - 🎁 集成Prettier作为代码格式化工具;
55 | - 🎁 集成Commitlint作为Git commit message规范校验;
56 | - 🎁 集成Changelog作为自动生成CHANGELOG.md;
57 | - 🎁 集成Husky作为Git commit message规范校验;
58 | - 🎁 集成Lint-staged作为Git commit message规范校验;
59 | - 🎁 集成Prettier作为Git commit message规范校验;
60 | - 🎁 集成Nuxt PWA作为PWA支持;
61 | - 🎁 集成Nuxt Composition API作为Vue3 Composition API支持;
62 | - 🎁 集成Nuxt Image作为图片优化支持;
63 | - 🎁 集成Nuxt Webfont loader作为字体加载支持;
64 | - 🎁 集成Nuxt Style Resources作为全局样式支持;
65 | - 🎁 集成Nuxt Google Analytics作为Google Analytics支持;
66 | - 🎁 集成Nuxt Google Tag Manager作为Google Tag Manager支持;
67 | - 🎁 集成Nuxt Sentry作为Sentry支持;
68 | - 🎁 集成Nuxt Sitemap作为Sitemap支持;
69 | - 🎁 集成Nuxt Robots作为Robots支持;
70 |
71 | ### 如何使用
72 |
73 | ```bash
74 | # 克隆项目
75 | git clone https://github.com/Createitv/nuxt3-tailwind-starter
76 | # 进入项目目录
77 | cd nuxt3-tailwind-starter
78 | # 安装依赖
79 | pnpm install
80 | # 启动服务
81 | pnpm dev
82 | ```
83 |
84 | ### 如何部署
85 |
86 | ```bash
87 | # 构建
88 | pnpm build
89 | # 启动服务
90 | pnpm start
91 | ```
92 |
93 | ### 如何查看
94 |
95 | ```bash
96 | # 启动服务
97 | pnpm dev
98 | # 打开浏览器
99 | http://localhost:3000
100 | ```
101 |
102 | ### 如何提交
103 |
104 | ```bash
105 | # 提交代码
106 | git add .
107 | git commit -m 'feat: xxx'
108 | git push
109 | ```
110 | ### 如何发布
111 |
112 | ```bash
113 | # 发布代码
114 | git tag v1.0.0
115 | git push origin v1.0.0
116 | ```
117 | ### Github Action自动部署
118 |
119 | ```yml
120 | jobs:
121 | deploy:
122 | runs-on: ubuntu-latest
123 | steps:
124 | - uses: actions/checkout@v2
125 | # your build commands
126 | # - run: |
127 | # ng build --prod
128 | - uses: amondnet/vercel-action@v20 # deploy
129 | with:
130 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
131 | github-token: ${{ secrets.GITHUB_TOKEN }} # Optional
132 | vercel-args: --prod # Optional
133 | vercel-org-id: ${{ secrets.ORG_ID}} # Required
134 | vercel-project-id: ${{ secrets.PROJECT_ID}} # Required
135 | working-directory: .
136 | ```
137 |
138 | 上传Vecel生成的`VERCEL_TOKEN` `ORG_ID ` `PROJECT_ID`到项目的` Action secrets`每次推送`Main`分支自动同步部署到网站上
139 |
140 |
--------------------------------------------------------------------------------
/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/assets/css/tailwind.css:
--------------------------------------------------------------------------------
1 | @import 'windplus/styles/main';
2 |
3 | body {
4 | @apply text-gray-900 bg-gray-100 dark:text-white;
5 | }
6 |
7 | @layer base {
8 | :root {
9 | --color-text-base: #171717;
10 | --color-fill: #ffffff;
11 | --color-icon-fill: #1d4ed8;
12 | --color-text-icon: #ffffff;
13 | }
14 | }
15 |
16 | .feature-blue {
17 | --color-text-base: #ffffff;
18 | --color-fill: #2563eb;
19 | --color-icon-fill: #1d4ed8;
20 | --color-text-icon: #ffffff;
21 | }
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] }
2 |
--------------------------------------------------------------------------------
/components/App/Footer.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
11 |
15 |
20 |
26 | {{ $t('app_name') }}
27 |
28 |
29 | {{ $t('app_name') }} by
30 |
35 | 彷`徨`
36 |
37 | . All rights reserved
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/components/App/Header.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
40 |
53 |
54 | {{ $t('app_name') }}
55 |
56 |
62 |
63 |
64 |
71 |
83 |
102 | {{ $t(menu.text) }}
103 |
104 |
105 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
130 |
--------------------------------------------------------------------------------
/components/App/LangSwitcher.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
22 |
23 | {{ lang }}
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/Button/Button.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import Button from './Button.vue'
3 |
4 | const variants = [
5 | 'default',
6 | 'primary',
7 | 'secondary',
8 | 'info',
9 | 'warning',
10 | 'success',
11 | 'dark',
12 | ]
13 |
14 | const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']
15 |
16 | export default {
17 | title: 'Components/Button',
18 | component: Button,
19 | args: {},
20 | }
21 |
22 | const Template: Story = (args, { argTypes }) => ({
23 | components: { Button },
24 | setup() {
25 | return { args, argTypes, variants }
26 | },
27 | template: `
28 |
29 |
30 | {{ variant }}
31 |
32 |
33 | `,
34 | })
35 |
36 | export const Default = Template.bind({})
37 | Default.args = {}
38 |
39 | export const Outlined = Template.bind({})
40 | Outlined.args = {
41 | outlined: true,
42 | }
43 |
44 | export const Text = Template.bind({})
45 | Text.args = {
46 | text: true,
47 | }
48 |
49 | export const Rounded = Template.bind({})
50 | Rounded.args = {
51 | rounded: true,
52 | }
53 |
54 | export const Block = Template.bind({})
55 | Block.args = {
56 | block: true,
57 | }
58 |
59 | export const Disabled = Template.bind({})
60 | Disabled.args = {
61 | disabled: true,
62 | }
63 |
64 | export const Sizes: Story = (args, { argTypes }) => ({
65 | components: { Button },
66 | setup() {
67 | return { args, argTypes, sizes }
68 | },
69 | template: `
70 |
71 |
72 | button: {{ size }}
73 |
74 |
75 | `,
76 | })
77 |
78 | export const Icon: Story = (args, { argTypes }) => ({
79 | components: { Button },
80 | setup() {
81 | return { args, argTypes, sizes }
82 | },
83 | template: `
84 |
85 |
91 |
94 |
95 |
96 | `,
97 | })
98 |
--------------------------------------------------------------------------------
/components/Button/Button.vue:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
297 |
--------------------------------------------------------------------------------
/components/Contact/Form.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/components/Dashboard/Header.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
29 |
44 |
45 |
46 |
47 |
48 |
61 |
65 |
66 | {{ auth.user.name }}
67 |
71 |
72 |
73 |
74 |
75 | Profile
76 | Setting
77 |
78 |
79 | Logout
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/components/Dashboard/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
71 |
72 |
73 |
74 |
75 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/components/Dashboard/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/components/Dashboard/SidebarLink.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
28 |
29 |
30 |
31 | {{ text }}
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/components/Dashboard/Stats/Item.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
14 |
18 |
19 |
20 |
21 |
22 | {{ count }}
23 |
24 |
25 | {{ text }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/components/Form/Input.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 | {{ label }}
26 |
27 |
39 |
40 | {{ errorMessage }}
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/components/Form/Switch.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
38 | {{ srText }}
39 |
44 |
45 |
46 | {{ errorMessage }}
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/components/HeroSection/HeroSection.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
21 |
24 |
25 |
26 | {{ title }}
27 |
28 |
29 | {{ description }}
30 |
31 |
32 |
33 | {{ button.text }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
51 |
--------------------------------------------------------------------------------
/components/Home/About.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ title }}
18 |
19 |
20 | {{ description }}
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/components/Home/Features.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
25 |
26 |
27 |
34 |
35 | {{ feature.name }}
36 |
37 |
38 | {{ feature.description }}
39 |
40 |
41 |
42 |
47 |
67 |
68 |
69 | {{ item.title }}
70 |
71 |
72 | {{ item.description }}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
84 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/components/Home/Quote.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ title }}
14 |
15 |
16 | {{ description }}
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/PageHeader/PageHeader.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | {{ title }}
14 |
15 |
16 | {{ subtitle }}
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/SectionHeader.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ title }}
14 |
15 |
16 | {{ description }}
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/UI/Autocomplete/Autocomplete.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import Autocomplete from './Autocomplete.vue'
3 |
4 | const items = [
5 | { id: 1, text: 'Wade Cooper' },
6 | { id: 2, text: 'Arlene Mccoy' },
7 | { id: 3, text: 'Devon Webb' },
8 | { id: 4, text: 'Tom Cook' },
9 | { id: 5, text: 'Tanya Fox' },
10 | { id: 6, text: 'Hellen Schmidt' },
11 | ]
12 |
13 | export default {
14 | title: 'Components/Autocomplete',
15 | component: Autocomplete,
16 | args: {
17 | items,
18 | modelValue: false,
19 | searchBy: 'text',
20 | displayText: 'text',
21 | placeholder: 'Search...',
22 | label: '',
23 | rules: '',
24 | noDataText: 'No data.',
25 | notFoundText: 'Nothing found.',
26 | },
27 | }
28 |
29 | const Template: Story = args => ({
30 | // Components used in your story `template` are defined in the `components` object
31 | components: { Autocomplete },
32 | // The story's `args` need to be mapped into the template through the `setup()` method
33 | setup() {
34 | return { args }
35 | },
36 | // And then the `args` are bound to your component with `v-bind="args"`
37 | template: ' ',
38 | })
39 |
40 | export const Default = Template.bind({})
41 | Default.args = {}
42 |
--------------------------------------------------------------------------------
/components/UI/Autocomplete/Autocomplete.vue:
--------------------------------------------------------------------------------
1 |
87 |
88 |
89 |
90 |
91 | {{ label }}
92 |
93 |
94 |
114 |
129 |
130 |
145 |
146 |
147 |
148 |
153 |
154 |
155 |
156 |
162 |
180 |
184 | {{ noDataText }}
185 |
186 |
187 |
191 | {{ notFoundText }}
192 |
193 |
194 |
201 |
208 |
215 | {{ item[displayText] }}
216 |
217 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 | {{ errorMessage }}
239 |
240 |
241 |
--------------------------------------------------------------------------------
/components/UI/Card/Card.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
42 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
60 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/components/UI/Card/CardBody.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/UI/Card/CardFooter.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/UI/Card/CardHeader.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | {{ title }}
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/UI/Collapsible/CollapseTransition.vue:
--------------------------------------------------------------------------------
1 |
294 |
295 |
296 |
311 |
312 |
313 |
314 |
--------------------------------------------------------------------------------
/components/UI/Collapsible/Collapsible.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import Collapsible from './Collapsible.vue'
3 |
4 | export default {
5 | title: 'Components/Collapsible',
6 | component: Collapsible,
7 | args: {
8 | modelValue: false,
9 | title: 'Item',
10 | content: 'lorem ipsum dolor sit amet',
11 | },
12 | }
13 |
14 | const Template: Story = (args, { argTypes }) => ({
15 | components: { Collapsible },
16 | setup() {
17 | return { args, argTypes }
18 | },
19 | template: `
20 |
21 | `,
22 | })
23 |
24 | export const Default = Template.bind({})
25 | Default.args = {}
26 |
--------------------------------------------------------------------------------
/components/UI/Collapsible/Collapsible.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
75 |
76 | {{ title }}
77 |
78 |
83 |
84 |
85 |
86 |
87 | {{ content }}
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/components/UI/Collapsible/CollapsibleGroup.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import CollapsibleGroup from './CollapsibleGroup.vue'
3 |
4 | const genItems = (length = 5): any[] =>
5 | Array.from({ length }, (_, v) => ({
6 | title: `Item ${v + 1}`,
7 | content: `lorem ipsum ${v + 1}`,
8 | }))
9 |
10 | const items = genItems(5)
11 |
12 | export default {
13 | title: 'Components/CollapsibleGroup',
14 | component: CollapsibleGroup,
15 | args: {
16 | modelValue: false,
17 | accordion: false,
18 | items,
19 | },
20 | }
21 |
22 | const Template: Story = (args, { argTypes }) => ({
23 | components: { CollapsibleGroup },
24 | setup() {
25 | return { args, argTypes }
26 | },
27 | template: `
28 |
29 | `,
30 | })
31 |
32 | export const Default = Template.bind({})
33 | Default.args = {}
34 |
35 | export const Accordion = Template.bind({})
36 | Accordion.args = {
37 | accordion: true,
38 | }
39 |
--------------------------------------------------------------------------------
/components/UI/Collapsible/CollapsibleGroup.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/components/UI/Dropdown/Dropdown.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import Dropdown from './Dropdown.vue'
3 | import DropdownItem from './DropdownItem.vue'
4 | import type { DropdownItemProps } from './types'
5 |
6 | const icons = ['calendar', 'attachment', 'download', 'clock', 'document']
7 |
8 | const genItems = (length = 5): DropdownItemProps[] =>
9 | Array.from({ length }, (_, v) => ({
10 | text: `Item ${v + 1}`,
11 | icon: icons[Math.floor(Math.random() * icons.length)],
12 | }))
13 |
14 | const items = [...genItems(2), { divider: true }, ...genItems(3)]
15 |
16 | export default {
17 | title: 'Components/Dropdown',
18 | component: Dropdown,
19 | args: {
20 | modelValue: false,
21 | right: false,
22 | btnProps: {
23 | variant: 'secondary',
24 | },
25 | label: 'Options',
26 | items,
27 | },
28 | }
29 |
30 | const Template: Story = (args, { argTypes }) => ({
31 | components: { Dropdown },
32 | setup() {
33 | return { args, argTypes }
34 | },
35 | template: `
36 |
37 |
38 |
39 | `,
40 | })
41 |
42 | export const Default = Template.bind({})
43 | Default.args = {}
44 |
45 | export const Right = Template.bind({})
46 | Right.args = {
47 | right: true,
48 | }
49 |
50 | export const RouterLink = Template.bind({})
51 | RouterLink.args = {
52 | items: [
53 | {
54 | text: 'Link 1',
55 | to: '/home',
56 | },
57 | ],
58 | }
59 |
60 | export const Href = Template.bind({})
61 | Href.args = {
62 | items: [
63 | {
64 | text: 'Link 1',
65 | href: '/home',
66 | },
67 | {
68 | text: 'Link 2',
69 | href: '/setting',
70 | newTab: true,
71 | },
72 | ],
73 | }
74 |
75 | export const Slots: Story = (args, { argTypes }) => ({
76 | components: { Dropdown, DropdownItem },
77 | setup() {
78 | return { args, argTypes }
79 | },
80 | template: `
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | `,
92 | })
93 |
--------------------------------------------------------------------------------
/components/UI/Dropdown/Dropdown.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ label }}
32 |
37 |
38 |
39 |
40 |
41 |
49 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/components/UI/Dropdown/DropdownButton.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/UI/Dropdown/DropdownItem.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
48 |
49 |
50 |
51 | {{ text }}
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/components/UI/Dropdown/types.d.ts:
--------------------------------------------------------------------------------
1 | export type DropdownProps = {
2 | modelValue: boolean;
3 | btnProps: any;
4 | label: string;
5 | right: boolean;
6 | items?: DropdownItemProps[];
7 | };
8 |
9 | export type DropdownItemProps = {
10 | text: string;
11 | to?: string;
12 | href?: string;
13 | icon?: string;
14 | newTab?: boolean;
15 | };
16 |
--------------------------------------------------------------------------------
/components/UI/Modal/Backdrop.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/components/UI/Modal/Modal.vue:
--------------------------------------------------------------------------------
1 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | {{ activator }}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
82 |
83 |
84 |
85 |
86 |
87 |
96 |
100 |
101 |
106 |
107 |
111 | {{ title }}
112 |
113 |
114 |
115 |
120 | ×
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {{ closeText }}
133 |
134 |
141 | {{ confirmText }}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/components/UI/NavDrawer/NavDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
266 |
267 |
268 |
269 | Logout
270 |
271 |
272 |
273 |
274 |
--------------------------------------------------------------------------------
/components/UI/Select/Select.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Story } from '@storybook/vue3'
2 | import Select from './Select.vue'
3 | import type { SelectItem } from './types'
4 |
5 | const genItems = (length = 5): SelectItem[] =>
6 | Array.from({ length }, (_, v) => ({
7 | text: `Item ${v + 1}`,
8 | value: v,
9 | }))
10 |
11 | const items = [...genItems(2), { divider: true }, ...genItems(3)]
12 |
13 | export default {
14 | title: 'Components/Select',
15 | component: Select,
16 | args: {
17 | modelValue: null,
18 | label: 'Choose',
19 | items,
20 | },
21 | }
22 |
23 | const Template: Story = (args, { argTypes }) => ({
24 | components: { Select },
25 | setup() {
26 | return { args, argTypes }
27 | },
28 | template: `
29 |
30 |
31 |
32 | `,
33 | })
34 |
35 | export const Default = Template.bind({})
36 | Default.args = {}
37 |
38 | export const Selected = Template.bind({})
39 | Selected.args = {
40 | modelValue: items[0],
41 | }
42 |
43 | // export const Slots: Story = (args, { argTypes }) => ({
44 | // components: { Select, SelectItem },
45 | // setup() {
46 | // return { args, argTypes };
47 | // },
48 | // template: `
49 | //
50 | //
51 | //
52 | //
53 | //
54 | //
55 | //
56 | //
57 | //
58 | //
59 | // `,
60 | // });
61 |
--------------------------------------------------------------------------------
/components/UI/Select/Select.vue:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
47 |
48 |
75 | {{ selected?.text || placeholder }}
76 |
87 |
92 |
93 |
94 |
95 |
100 |
118 |
125 |
126 |
127 |
134 |
139 | {{ item.text }}
140 |
141 |
153 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/components/UI/Select/types.d.ts:
--------------------------------------------------------------------------------
1 | export type SelectItem = {
2 | text: string;
3 | value: string | number;
4 | divider?: boolean;
5 | };
6 |
--------------------------------------------------------------------------------
/components/blog/PostImage.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/components/blog/PostItem.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
15 |
16 |
17 |
Article
18 |
19 | {{ post.attributes.title }}
20 |
21 |
22 | {{ post.attributes.excerpt || post.attributes.body.substr(0, 150) }}
23 |
24 |
25 |
32 |
33 |
34 | Author Name
35 |
36 |
37 | {{ new Date(post.attributes.publishedAt).toDateString() }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/composables/auth.ts:
--------------------------------------------------------------------------------
1 | import type { AuthUser } from '~~/stores/auth'
2 |
3 | export const useAuthStorage = (
4 | { authTokenKey, authUserKey } = {
5 | authTokenKey: 'auth.token',
6 | authUserKey: 'auth.user',
7 | },
8 | ) => {
9 | const user = useCookie(authUserKey)
10 | const token = useCookie(authTokenKey)
11 |
12 | const store = (newToken: string, newUser: Record) => {
13 | token.value = newToken
14 | user.value = newUser
15 | }
16 |
17 | const clear = () => {
18 | user.value = null
19 | token.value = ''
20 | }
21 |
22 | return { store, clear, user, token }
23 | }
24 |
--------------------------------------------------------------------------------
/composables/states.ts:
--------------------------------------------------------------------------------
1 | export const useSidebar = () => useState('sidebar', () => false)
2 |
--------------------------------------------------------------------------------
/composables/strapi.ts:
--------------------------------------------------------------------------------
1 | import type { Image } from '~~/types'
2 |
3 | export const getStrapiUrl = (image: Image) => {
4 | const config = useRuntimeConfig()
5 |
6 | return config.strapi.url + image.data.attributes.url
7 | }
8 |
--------------------------------------------------------------------------------
/content/articles/base.md:
--------------------------------------------------------------------------------
1 | # Hello Content V2
2 |
3 | lorem
4 |
--------------------------------------------------------------------------------
/content/articles/index.md:
--------------------------------------------------------------------------------
1 | # Blog
2 |
3 | [Nuxt3-start-Template](/nuxt-template)
4 |
5 | [Base](/base)
6 |
--------------------------------------------------------------------------------
/content/articles/nuxt-template.md:
--------------------------------------------------------------------------------
1 | # Nuxt3 Tailwind Starter
2 |
3 | 你是否受够了每次前端开发`npm install [..args]`,重复配置各种前端基本组件库、Lint库、重复部署上线。在N
4 | 次以后配置一下午做了一个有好的`Nuxt3`开发Template。
5 |
6 | ## 项目预览
7 |
8 | [演示地址](https://nuxt3-tailwind-start.vercel.app/) [直接修改](https://stackblitz.com/github/Createitv/nuxt3-tailwind-starter/tree/main)
9 |
10 | ## 功能支持
11 |
12 | - [Nuxt 3](https://v3.nuxtjs.org/)
13 | - [Nuxt Content v2](https://content.nuxtjs.org/)
14 | - [Tailwind CSS](https://tailwindcss.com/)
15 | - [Nuxt Icon](https://nuxt.com/modules/icon)
16 | - [Pinia](https://pinia.vuejs.org/)
17 | - [vee-validate](https://vee-validate.logaretm.com/v4/)
18 | - `useAuthStore` 登录验证
19 | - [@nuxtjs/i18n](https://v8.i18n.nuxtjs.org/)
20 |
21 | ### 其他
22 |
23 | - 🎨 使用Tailwind作为CSS组件库
24 | - 📦 内置多种通用组件和布局
25 | - 💪 Eslint和Stylelint代码检验;
26 | - 🐶 创建Git commit message cn规范校验;
27 | - 🎉 天然支持Vue3、Typescript、Vite等;
28 | - 🍍 集成Pinia作为状态管理;
29 | - 🥤 集成vueuse作为Hooks库;
30 | - 🎊文件式路由、componentsAPI自动导入、组件自动导入等;
31 | - 🎁 集成Nuxt Content作为Markdown文件管理;
32 | - 🎁 集成Nuxt Icon作为SVG图标管理;
33 | - 🎁 集成Nuxt I18n作为国际化管理;
34 | - 🎁 集成Vee-Validate作为表单验证;
35 | - 🎁 集成Vercel作为自动部署;
36 | - 🎁 集成Pnpm作为包管理工具;
37 | - 🎁 集成Github Action作为CI/CD;
38 | - 🎁 集成Prettier作为代码格式化工具;
39 | - 🎁 集成Commitlint作为Git commit message规范校验;
40 | - 🎁 集成Changelog作为自动生成CHANGELOG.md;
41 | - 🎁 集成Husky作为Git commit message规范校验;
42 | - 🎁 集成Lint-staged作为Git commit message规范校验;
43 | - 🎁 集成Prettier作为Git commit message规范校验;
44 | - 🎁 集成Nuxt PWA作为PWA支持;
45 | - 🎁 集成Nuxt Composition API作为Vue3 Composition API支持;
46 | - 🎁 集成Nuxt Image作为图片优化支持;
47 | - 🎁 集成Nuxt Webfont loader作为字体加载支持;
48 | - 🎁 集成Nuxt Style Resources作为全局样式支持;
49 | - 🎁 集成Nuxt Google Analytics作为Google Analytics支持;
50 | - 🎁 集成Nuxt Google Tag Manager作为Google Tag Manager支持;
51 | - 🎁 集成Nuxt Sentry作为Sentry支持;
52 | - 🎁 集成Nuxt Sitemap作为Sitemap支持;
53 | - 🎁 集成Nuxt Robots作为Robots支持;
54 |
55 | ### 项目预览
56 |
57 | 
58 |
59 | ### 如何使用
60 |
61 | ```bash
62 | # 克隆项目
63 | git clone https://github.com/Createitv/nuxt3-tailwind-starter
64 | # 进入项目目录
65 | cd nuxt3-tailwind-starter
66 | # 安装依赖
67 | pnpm install
68 | # 启动服务
69 | pnpm dev
70 | ```
71 |
72 | ### 如何部署
73 |
74 | ```bash
75 | # 构建
76 | pnpm build
77 | # 启动服务
78 | pnpm start
79 | ```
80 |
81 | ### 如何查看
82 |
83 | ```bash
84 | # 启动服务
85 | pnpm dev
86 | # 打开浏览器
87 | http://localhost:3000
88 | ```
89 |
90 | ### 如何提交
91 |
92 | ```bash
93 | # 提交代码
94 | git add .
95 | git commit -m 'feat: xxx'
96 | git push
97 | ```
98 |
99 | ### 如何发布
100 |
101 | ```bash
102 | # 发布代码
103 | git tag v1.0.0
104 | git push origin v1.0.0
105 | ```
106 |
107 | ### Gihub Action自动部署
108 |
109 | ```yml
110 | jobs:
111 | deploy:
112 | runs-on: ubuntu-latest
113 | steps:
114 | - uses: actions/checkout@v2
115 | # your build commands
116 | # - run: |
117 | # ng build --prod
118 | - uses: amondnet/vercel-action@v20 # deploy
119 | with:
120 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
121 | github-token: ${{ secrets.GITHUB_TOKEN }} # Optional
122 | vercel-args: --prod # Optional
123 | vercel-org-id: ${{ secrets.ORG_ID}} # Required
124 | vercel-project-id: ${{ secrets.PROJECT_ID}} # Required
125 | working-directory: .
126 | ```
127 |
128 | 上传Vecel生成的`VERCEL_TOKEN` `ORG_ID ` `PROJECT_ID`到项目的` Action secrets`每次推送`Main`分支自动同步部署到网站上
129 |
--------------------------------------------------------------------------------
/content/wind/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | # Wind
5 |
6 | # da
7 |
8 | ## 12312
9 |
--------------------------------------------------------------------------------
/layouts/auth.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/layouts/dashboard.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/middleware/auth.ts:
--------------------------------------------------------------------------------
1 | import { useAuthStore } from '~~/stores/auth'
2 |
3 | export default defineNuxtRouteMiddleware((to) => {
4 | const auth = useAuthStore()
5 | const router = useRouter()
6 |
7 | if (!auth.loggedIn) {
8 | return router.push({
9 | path: '/auth/login',
10 | query: {
11 | next: to.path,
12 | },
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/middleware/guest.ts:
--------------------------------------------------------------------------------
1 | import { useAuthStore } from '~~/stores/auth'
2 |
3 | export default defineNuxtRouteMiddleware((to) => {
4 | const auth = useAuthStore()
5 | const router = useRouter()
6 |
7 | if (auth.loggedIn) {
8 | return router.push({
9 | path: '/dashboard',
10 | query: {
11 | from: to.path,
12 | },
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | export default defineNuxtConfig({
2 | // buildModules: ['@vueuse/nuxt', '@nuxtjs/strapi'],
3 | modules: [
4 | '@nuxt/content',
5 | '@nuxtjs/tailwindcss',
6 | '@vueuse/nuxt',
7 | '@pinia/nuxt',
8 | 'nuxt-icon',
9 | '@nuxtjs/i18n',
10 | // '@nuxtjs/robots',
11 | // '@nuxtjs/image',
12 | ],
13 | // buildModules: ['@nuxtjs/google-analytics', '@vueuse/nuxt'],
14 | // strapi: {
15 | // url: process.env.STRAPI_URL || 'https://bapi.warsono.id',
16 | // prefix: '/api',
17 | // version: 'v4',
18 | // cookie: {},
19 | // },
20 | i18n: {
21 | locales: ['en', 'id'],
22 | defaultLocale: 'en',
23 | vueI18n: {
24 | legacy: false,
25 | locale: 'en',
26 | messages: {
27 | en: {
28 | app_name: 'Nuxt Tailwind Kit',
29 | app_description:
30 | 'Quick Boilerplate built on top of Nuxt 3 and Tailwind CSS',
31 | menu_home: 'Home',
32 | menu_store: 'Store',
33 | menu_blog: 'Blog',
34 | menu_playground: 'playground',
35 | menu_dashboard: 'Dashboard',
36 | },
37 | id: {
38 | app_name: 'Nuxt Tailwind Kit',
39 | app_description:
40 | 'Boilerplate cepat yang dibangun dari Nuxt 3 and Tailwind CSS',
41 | menu_home: 'Beranda',
42 | menu_store: 'Toko',
43 | menu_blog: 'Blog',
44 | menu_playground: 'playground',
45 | menu_dashboard: 'Dasbor',
46 | },
47 | },
48 | },
49 | },
50 | runtimeConfig: {
51 | public: {
52 | gaId: '',
53 | },
54 | },
55 | // googleAnalytics: {
56 | // id: process.env.GOOGLE_ANALYTICS_ID, // Use as fallback if no runtime
57 | // config is provided }, publicRuntimeConfig: { googleAnalytics: { id:
58 | // process.env.GOOGLE_ANALYTICS_ID, }, },
59 | content: {
60 | // https://content.nuxtjs.org/api/configuration
61 | },
62 | })
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "nuxi dev",
5 | "build": "nuxi build",
6 | "start": "node .output/server/index.mjs",
7 | "generate": "nuxt generate",
8 | "preview": "nuxt preview",
9 | "storybook": "start-storybook -p 6006",
10 | "build-storybook": "build-storybook",
11 | "lint": "eslint .",
12 | "lint:fix": "eslint . --fix",
13 | "prepare": "npx husky install",
14 | "commit": "git add . && pnpm lint:fix && git cz",
15 | "genlog": "conventional-changelog -p angular -i CHANGELOG.md -s",
16 | "git:push": "npm run genlog && git push origin main",
17 | "deploy": "npx vercel --prod"
18 | },
19 | "dependencies": {
20 | "@headlessui/vue": "^1.6.7",
21 | "@nuxtjs/i18n": "8.0.0-alpha.1",
22 | "@nuxtjs/robots": "^3.0.0",
23 | "@pinia/nuxt": "^0.4.1",
24 | "@vueuse/core": "^9.1.1",
25 | "highlight.js": "^11.6.0",
26 | "markdown-it": "^13.0.1",
27 | "markdown-it-prism": "^2.2.5",
28 | "pinia": "^2.0.21",
29 | "prism-themes": "^1.9.0",
30 | "vee-validate": "^4.6.7",
31 | "vue": "^3.2.37",
32 | "vue-gtag": "^2.0.1",
33 | "windplus": "^0.0.2",
34 | "yup": "^0.32.11"
35 | },
36 | "devDependencies": {
37 | "@antfu/eslint-config": "^0.29.3",
38 | "@babel/core": "^7.18.13",
39 | "@commitlint/cli": "^17.1.2",
40 | "@commitlint/config-conventional": "^17.1.0",
41 | "@iconify/json": "^2.1.100",
42 | "@nuxt/content": "^2.2.2",
43 | "@nuxt/image": "^0.7.1",
44 | "@nuxtjs/google-analytics": "^2.4.0",
45 | "@nuxtjs/sitemap": "^2.4.0",
46 | "@nuxtjs/strapi": "^1.5.0",
47 | "@nuxtjs/tailwindcss": "^6.1.3",
48 | "@storybook/addon-actions": "^6.5.10",
49 | "@storybook/addon-essentials": "^6.5.10",
50 | "@storybook/addon-interactions": "^6.5.10",
51 | "@storybook/addon-links": "^6.5.10",
52 | "@storybook/builder-vite": "^0.2.2",
53 | "@storybook/testing-library": "^0.0.13",
54 | "@storybook/vue3": "^6.5.10",
55 | "@tailwindcss/forms": "^0.5.2",
56 | "@tailwindcss/typography": "^0.5.8",
57 | "@types/react": "file:stub/types__react",
58 | "@vueuse/nuxt": "^9.1.1",
59 | "babel-loader": "^8.2.5",
60 | "commitizen": "^4.2.5",
61 | "conventional-changelog-cli": "^2.2.2",
62 | "cz-conventional-changelog-zh": "^0.0.2",
63 | "eslint": "8.18.0",
64 | "husky": "^8.0.2",
65 | "nuxt": "^3.0.0",
66 | "nuxt-icon": "^0.1.5",
67 | "prettier-plugin-tailwindcss": "^0.2.0",
68 | "sitemap": "^7.1.1",
69 | "swiper": "^8.3.2",
70 | "tailwindcss": "^3.1.8",
71 | "vercel": "^28.7.0",
72 | "vue-loader": "^17.0.0"
73 | },
74 | "config": {
75 | "commitizen": {
76 | "path": "./node_modules/cz-conventional-changelog-zh"
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pages/401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Unauthenticated
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pages/403.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Permission Denied
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pages/auth/forgot-password.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Forgot Password
31 |
32 |
33 | Please enter your email
34 |
35 |
36 |
37 |
38 |
39 |
40 | Submit
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/pages/auth/login.vue:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Login
56 |
57 |
58 | Please enter your credentials
59 |
60 |
61 |
62 |
63 | {{ error }}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
75 | Remember me
76 |
77 |
78 | Forgot Password?
79 |
80 |
81 |
82 |
83 | Login
84 |
85 |
86 |
91 |
92 |
93 | Don't have account?
94 |
95 | Register
96 |
97 |
98 |
99 |
100 |
101 |
102 |
104 |
--------------------------------------------------------------------------------
/pages/auth/register.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Register
33 |
34 |
35 | Please enter your credentials
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
54 |
55 | I agree with
56 |
60 | Terms and Condition
61 |
62 |
63 |
64 |
65 |
66 | Login
67 |
68 |
69 |
70 | Already have an account?
71 |
75 | Login
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/pages/auth/reset-password.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Forgot Password
33 |
34 |
35 | Please enter your email
36 |
37 |
38 |
39 |
40 |
41 |
46 |
47 |
48 | Submit
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/pages/blog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | TypeScript一路向北
4 |
5 |
6 | {{ $route.params.slug }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/pages/counter.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | Counter
11 |
12 |
13 | {{ counter.count }}
14 |
15 |
16 |
17 | -
18 |
19 |
20 | +
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/pages/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 | Dashboard
40 |
41 |
42 | Overview & summary
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
55 |
56 |
57 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita sit esse
58 | voluptatem et deserunt earum explicabo aut quis laboriosam nostrum
59 | quisquam sequi maxime est vitae, vero reiciendis veniam repudiandae fugit!
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/pages/forms.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Profile
10 |
11 |
12 | This information will be displayed publicly so be careful what you
13 | share.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 | Website
28 |
29 |
30 |
33 | http://
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
51 | About
52 |
53 |
54 |
61 |
62 |
63 | Brief description for your profile. URLs are hyperlinked.
64 |
65 |
66 |
67 |
68 |
69 | Photo
70 |
71 |
72 |
75 |
80 |
83 |
84 |
85 |
89 | Change
90 |
91 |
92 |
93 |
94 |
95 |
96 | Cover photo
97 |
98 |
101 |
102 |
109 |
115 |
116 |
117 |
121 | Upload a file
122 |
128 |
129 |
130 | or drag and drop
131 |
132 |
133 |
134 | PNG, JPG, GIF up to 10MB
135 |
136 |
137 |
138 |
139 |
140 |
141 |
145 | Save
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | Personal Information
166 |
167 |
168 | Use a permanent address where you can receive mail.
169 |
170 |
171 |
172 |
173 |
174 |
175 |
293 |
294 |
298 | Save
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
312 |
313 |
314 |
315 |
316 |
317 |
318 | Notifications
319 |
320 |
321 | Decide which communications you'd like to receive and how.
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 | By Email
332 |
333 |
334 |
335 |
336 |
342 |
343 |
344 |
Comments
345 |
346 | Get notified when someones posts a comment on a posting.
347 |
348 |
349 |
350 |
351 |
352 |
358 |
359 |
360 |
Candidates
361 |
362 | Get notified when a candidate applies for a job.
363 |
364 |
365 |
366 |
367 |
368 |
374 |
375 |
376 |
Offers
377 |
378 | Get notified when a candidate accepts or rejects an
379 | offer.
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 | Push Notifications
389 |
390 |
391 | These are delivered via SMS to your mobile phone.
392 |
393 |
394 |
438 |
439 |
440 |
441 |
445 | Save
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
--------------------------------------------------------------------------------
/pages/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/pages/playground/index.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
Playground
21 |
27 |
28 |
29 |
30 |
35 |
--------------------------------------------------------------------------------
/pages/store/[slug].vue:
--------------------------------------------------------------------------------
1 |
24 |
85 |
86 |
87 |
88 |
89 |
90 |
104 |
105 |
124 |
125 |
126 |
131 | {{ product.name }}
132 |
133 |
134 |
135 |
136 |
137 |
138 |
150 |
159 |
164 |
165 |
166 |
167 |
172 |
173 |
174 |
179 |
180 |
181 |
189 |
194 |
195 |
196 |
197 |
198 |
214 |
215 |
226 |
227 |
228 |
229 |
230 |
231 | Product information
232 |
233 |
234 | {{ product.price }}
235 |
236 |
237 |
238 |
239 |
240 | Reviews
241 |
242 |
270 |
271 |
272 |
273 |
274 |
275 |
276 | Color
277 |
278 |
279 |
280 |
281 | Choose a color
282 |
283 |
284 |
291 |
298 |
299 | {{ color.name }}
300 |
301 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
329 |
330 |
331 |
332 | Choose a size
333 |
334 |
337 |
345 |
353 |
354 | {{ size.name }}
355 |
356 |
364 |
375 |
388 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
424 | Add to bag
425 |
426 |
427 |
428 |
429 |
439 |
440 |
441 |
442 | Description
443 |
444 |
445 |
446 |
447 | {{ product.description }}
448 |
449 |
450 |
451 |
452 |
453 |
454 | Highlights
455 |
456 |
457 |
458 |
459 |
464 | {{ highlight }}
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 | Details
473 |
474 |
475 |
476 |
477 | {{ product.details }}
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
--------------------------------------------------------------------------------
/pages/store/index.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 | {{ header.title }}
12 |
13 |
14 | {{ header.description }}
15 |
16 |
17 |
18 |
21 |
27 |
30 |
35 |
36 |
37 | {{ product.name }}
38 |
39 |
40 | {{ product.price }}
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/pages/swiper.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
35 |
36 |
42 |
43 |
44 |
45 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
63 | Prev
64 |
65 |
68 | Next
69 |
70 |
71 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/plugins/gtag.client.ts:
--------------------------------------------------------------------------------
1 | import VueGtag from 'vue-gtag'
2 |
3 | export default defineNuxtPlugin((nuxtApp) => {
4 | const config = useRuntimeConfig()
5 |
6 | nuxtApp.vueApp.use(VueGtag, {
7 | config: {
8 | id: config.public.gaId,
9 | },
10 | }, nuxtApp.$router)
11 | })
12 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | // prettier.config.js
2 | module.exports = {
3 | plugins: [require('prettier-plugin-tailwindcss')],
4 | printWidth: 80, // 单行长度
5 | tabWidth: 2, // 缩进长度
6 | useTabs: false, // 使用空格代替tab缩进
7 | semi: false, // 句末使用分号
8 | singleQuote: true, // 使用单引号
9 | quoteProps: 'as-needed', // 仅在必需时为对象的key添加引号
10 | jsxSingleQuote: true, // jsx中使用单引号
11 | trailingComma: 'all', // 多行时尽可能打印尾随逗号
12 | bracketSpacing: true, // 在对象前后添加空格-eg: { foo: bar }
13 | jsxBracketSameLine: true, // 多属性html标签的‘>’折行放置
14 | arrowParens: 'always', // 单参数箭头函数参数周围使用圆括号-eg: (x) => x
15 | requirePragma: false, // 无需顶部注释即可格式化
16 | insertPragma: false, // 在已被preitter格式化的文件顶部加上标注
17 | proseWrap: 'preserve',
18 | htmlWhitespaceSensitivity: 'ignore', // 对HTML全局空白不敏感
19 | vueIndentScriptAndStyle: false, // 不对vue中的script及style标签缩进
20 | endOfLine: 'lf', // 结束行形式
21 | embeddedLanguageFormatting: 'auto', // 对引用代码进行格式化
22 | }
23 |
--------------------------------------------------------------------------------
/public/images/login-illustration.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
20 |
25 |
30 |
35 |
36 |
41 |
45 |
46 |
51 |
56 |
57 |
62 |
63 |
68 |
73 |
78 |
83 |
88 |
93 |
98 |
103 |
108 |
113 |
118 |
119 |
124 |
--------------------------------------------------------------------------------
/server/api/auth/login.post.ts:
--------------------------------------------------------------------------------
1 | export default defineEventHandler(async (event) => {
2 | const body = await useBody(event)
3 | if (body.email === 'admin@example.com' && body.password === 'secret') {
4 | return {
5 | data: {
6 | user: {
7 | name: 'Admin',
8 | email: body.email,
9 | },
10 | token: 'TOKEN1234#',
11 | },
12 | }
13 | }
14 |
15 | return {
16 | error: {
17 | message: 'Unauthenticated',
18 | },
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/server/api/home.get.ts:
--------------------------------------------------------------------------------
1 | export default defineEventHandler(() => {
2 | return {
3 | hero: {
4 | theme: 'default',
5 | title: 'nuxt3-tailwind-starter',
6 | description: 'nuxt3-tailwind-starter + Tailwind CSS',
7 | image:
8 | 'https://images.unsplash.com/photo-1614730321146-b6fa6a46bcb4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2874&q=80',
9 | buttons: [
10 | {
11 | text: 'Get Started',
12 | action: '#feature',
13 | props: {
14 | color: 'primary',
15 | size: 'xl',
16 | },
17 | },
18 | ],
19 | },
20 | about: {
21 | title: 'About',
22 | description:
23 | 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Obcaecati nesciunt assumenda exercitationem tenetur, sint, perspiciatis dolorem, esse eligendi ad sit id doloremque cum officiis accusamus. Nam ipsum vero fugit esse.',
24 | button: {
25 | text: 'Learn More',
26 | action: '#feature',
27 | },
28 | },
29 | feature: {
30 | title: 'Features',
31 | description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
32 | },
33 | features: [
34 | {
35 | name: 'Feature 1',
36 | description:
37 | 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
38 | image:
39 | 'https://images.unsplash.com/photo-1446941303752-a64bb1048d54?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1160&q=80',
40 | imagePosition: 'right',
41 | imageAlt: '',
42 | theme: '', // default
43 | items: [
44 | {
45 | title: 'Feature 1.1',
46 | description: 'Description 1.1',
47 | },
48 | {
49 | title: 'Feature 1.1',
50 | description: 'Description 1.1',
51 | },
52 | {
53 | title: 'Feature 1.1',
54 | description: 'Description 1.1',
55 | },
56 | ],
57 | },
58 | {
59 | name: 'Feature 2',
60 | description:
61 | 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
62 | image:
63 | 'https://images.unsplash.com/photo-1614315517650-3771cf72d18a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1548&q=80',
64 | imagePosition: 'left',
65 | imageAlt: '',
66 | theme: '', // default
67 | items: [
68 | {
69 | title: 'Feature 2.1',
70 | description: 'Description 2.1',
71 | },
72 | {
73 | title: 'Feature 2.1',
74 | description: 'Description 2.1',
75 | },
76 | {
77 | title: 'Feature 2.1',
78 | description: 'Description 2.1',
79 | },
80 | ],
81 | },
82 | {
83 | name: 'Feature 3',
84 | description:
85 | 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
86 | image:
87 | 'https://images.unsplash.com/photo-1614315517650-3771cf72d18a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1548&q=80',
88 | imagePosition: 'right',
89 | imageAlt: '',
90 | theme: 'feature-blue',
91 | items: [
92 | {
93 | title: 'Feature 3.1',
94 | description: 'Description 3.1',
95 | },
96 | {
97 | title: 'Feature 3.1',
98 | description: 'Description 3.1',
99 | },
100 | {
101 | title: 'Feature 3.1',
102 | description: 'Description 3.1',
103 | },
104 | ],
105 | },
106 | ],
107 | quote: {
108 | title: 'Quote Title',
109 | description:
110 | 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Libero enim quis incidunt fugiat, quisquam aspernatur eaque necessitatibus sit ipsum voluptas excepturi rerum in mollitia, iusto, id velit optio commodi odio',
111 | },
112 | contact: {
113 | title: 'Title',
114 | description:
115 | 'Give us a call or drop by anytime, we endeavour to answer all enquiries within 24 hours on business days.',
116 | email: 'app@example.com',
117 | phone: '+62-0000-111-2222',
118 | website: 'domain.tld',
119 | form: {
120 | title: 'Contact Us',
121 | description: 'Have question? Submit your question on this form bellow and we\'ll be in touch.',
122 | },
123 | },
124 | }
125 | })
126 |
--------------------------------------------------------------------------------
/server/api/store/products.get.ts:
--------------------------------------------------------------------------------
1 | const staticProducts = [
2 | {
3 | id: 1,
4 | name: 'Earthen Bottle',
5 | href: '#',
6 | price: '$48',
7 | imageSrc:
8 | 'https://tailwindui.com/img/ecommerce-images/category-page-04-image-card-01.jpg',
9 | imageAlt:
10 | 'Tall slender porcelain bottle with natural clay textured body and cork stopper.',
11 | },
12 | {
13 | id: 2,
14 | name: 'Nomad Tumbler',
15 | href: '#',
16 | price: '$35',
17 | imageSrc:
18 | 'https://tailwindui.com/img/ecommerce-images/category-page-04-image-card-02.jpg',
19 | imageAlt:
20 | 'Olive drab green insulated bottle with flared screw lid and flat top.',
21 | },
22 | {
23 | id: 3,
24 | name: 'Focus Paper Refill',
25 | href: '#',
26 | price: '$89',
27 | imageSrc:
28 | 'https://tailwindui.com/img/ecommerce-images/category-page-04-image-card-03.jpg',
29 | imageAlt:
30 | 'Person using a pen to cross a task off a productivity paper card.',
31 | },
32 | {
33 | id: 4,
34 | name: 'Machined Mechanical Pencil',
35 | href: '#',
36 | price: '$35',
37 | imageSrc:
38 | 'https://tailwindui.com/img/ecommerce-images/category-page-04-image-card-04.jpg',
39 | imageAlt:
40 | 'Hand holding black machined steel mechanical pencil with brass tip and top.',
41 | },
42 | // More products...
43 | ]
44 |
45 | export default defineEventHandler(() => {
46 | return {
47 | // products: Array.from({ length: 20 }, (v, k) => ({
48 | // name: `Product ${k + 1}`,
49 | // description: `Description ${k + 1}`,
50 | // prize: 20,
51 | // currency: 'USD',
52 | // })),
53 | products: staticProducts,
54 | header: {
55 | title: 'Store',
56 | description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quam accusantium, tenetur porro eum amet incidunt nemo, dolore ducimus vitae ipsa corporis obcaecati rem reprehenderit doloremque aliquid, aperiam officiis debitis commodi.',
57 | },
58 | }
59 | })
60 |
--------------------------------------------------------------------------------
/stores/auth.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export interface AuthUser extends Record {}
4 |
5 | export interface AuthState {
6 | loggedIn: boolean
7 | user: AuthUser | null
8 | loading: boolean
9 | }
10 |
11 | export const useAuthStore = defineStore({
12 | id: 'auth',
13 | state: (): AuthState => {
14 | const { token, user } = useAuthStorage()
15 |
16 | return {
17 | loggedIn: !!token.value,
18 | user: user.value,
19 | loading: false,
20 | }
21 | },
22 | actions: {
23 | logout() {
24 | const { clear } = useAuthStorage()
25 | clear()
26 |
27 | this.loggedIn = false
28 | this.user = null
29 | },
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/stores/counter.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useCounterStore = defineStore('counter', {
4 | state: () => ({ count: 0 }),
5 | actions: {
6 | increment() {
7 | this.count++
8 | },
9 | decrement() {
10 | this.count--
11 | },
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/stub/types__react/index.d.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/johnsoncodehk/volar/discussions/592
2 | export { }
3 |
--------------------------------------------------------------------------------
/stub/types__react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@types/react",
3 | "version": "0.0.0"
4 | }
5 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme')
2 | const colors = require('tailwindcss/colors')
3 | const primary = colors.blue
4 | const secondary = colors.pink
5 | const info = colors.sky
6 | const warning = colors.amber
7 | const success = colors.emerald
8 | const error = colors.red
9 |
10 | module.exports = {
11 | content: [
12 | './assets/**/*.{vue,js,css}',
13 | './components/**/*.{vue,js}',
14 | './layouts/**/*.vue',
15 | './pages/**/*.vue',
16 | './server/**/*.{js,ts}',
17 | './plugins/**/*.{js,ts}',
18 | './nuxt.config.{js,ts}',
19 | './node_modules/windplus/styles/**/*.{vue,js,css}',
20 | './node_modules/windplus/styles/*.{vue,js,css}',
21 | './node_modules/windplus/styles/main.css',
22 | ],
23 | darkMode: 'class', // or 'media' or 'class'
24 | theme: {
25 | extend: {
26 | colors: {
27 | primary,
28 | secondary,
29 | info,
30 | warning,
31 | success,
32 | error,
33 | },
34 | textColor: {
35 | skin: {
36 | base: 'var(--color-text-base)',
37 | muted: 'var(--color-text-muted)',
38 | active: 'var(--color-text-active)',
39 | hover: 'var(--color-text-hover)',
40 | icon: 'var(--color-text-icon)',
41 | },
42 | },
43 | backgroundColor: {
44 | skin: {
45 | 'fill': 'var(--color-fill)',
46 | 'fill-active': 'var(--color-fill-active)',
47 | 'fill-hover': 'var(--color-fill-hover)',
48 | 'icon-fill': 'var(--color-icon-fill)',
49 | },
50 | },
51 | fontFamily: {
52 | sans: ['Poppins', ...defaultTheme.fontFamily.sans],
53 | },
54 | },
55 | },
56 | variants: {
57 | extend: {},
58 | },
59 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
60 | }
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.nuxt/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface Large {
2 | ext: string
3 | url: string
4 | hash: string
5 | mime: string
6 | name: string
7 | path?: any
8 | size: number
9 | width: number
10 | height: number
11 | }
12 |
13 | export interface Small {
14 | ext: string
15 | url: string
16 | hash: string
17 | mime: string
18 | name: string
19 | path?: any
20 | size: number
21 | width: number
22 | height: number
23 | }
24 |
25 | export interface Medium {
26 | ext: string
27 | url: string
28 | hash: string
29 | mime: string
30 | name: string
31 | path?: any
32 | size: number
33 | width: number
34 | height: number
35 | }
36 |
37 | export interface Thumbnail {
38 | ext: string
39 | url: string
40 | hash: string
41 | mime: string
42 | name: string
43 | path?: any
44 | size: number
45 | width: number
46 | height: number
47 | }
48 |
49 | export interface Formats {
50 | large: Large
51 | small: Small
52 | medium: Medium
53 | thumbnail: Thumbnail
54 | }
55 |
56 | export interface Attributes2 {
57 | name: string
58 | alternativeText: string
59 | caption: string
60 | width: number
61 | height: number
62 | formats: Formats
63 | hash: string
64 | ext: string
65 | mime: string
66 | size: number
67 | url: string
68 | previewUrl?: any
69 | provider: string
70 | provider_metadata?: any
71 | createdAt: Date
72 | updatedAt: Date
73 | }
74 |
75 | export interface Data {
76 | id: number
77 | attributes: Attributes2
78 | }
79 |
80 | export interface Image {
81 | data: Data
82 | }
83 |
84 | export interface Attributes {
85 | title: string
86 | slug: string
87 | body: string
88 | excerpt: string
89 | createdAt: Date
90 | updatedAt: Date
91 | publishedAt: Date
92 | locale: string
93 | image: Image
94 | }
95 |
96 | export interface Post {
97 | id: number
98 | attributes: Attributes
99 | }
100 |
--------------------------------------------------------------------------------