├── .eslintrc ├── .npmrc ├── types ├── IResult.ts └── IUser.ts ├── public ├── 介绍.jpeg ├── 前端工程化.png ├── 大纲.jpeg ├── avatar.png ├── favicon.ico ├── qrcode.jpg ├── Docker部署.png ├── Node与服务器端.jpg ├── React全栈进阶.png ├── Vue3组件库实战.png ├── Vue源码全家桶.jpeg ├── webpack优化.jpeg ├── column-nuxt.png └── course-nuxt.png ├── .vercel └── output │ ├── static │ ├── favicon.ico │ └── _nuxt │ │ ├── app.config.e684eb09.js │ │ ├── detail.37523c12.js │ │ ├── index.d321cf39.js │ │ ├── error-component.3b96486c.js │ │ ├── error-500.5a01185b.js │ │ ├── error-500.aa16ed4d.css │ │ ├── error-404.7326de30.js │ │ ├── error-404.23f2309d.css │ │ └── nuxt-link.dd8747fa.js │ ├── functions │ └── __nitro.func │ │ ├── index.mjs.map │ │ ├── .vc-config.json │ │ ├── chunks │ │ ├── rollup │ │ │ ├── _virtual_head-static.mjs.map │ │ │ └── _virtual_head-static.mjs │ │ ├── app │ │ │ ├── _nuxt │ │ │ │ ├── error-404-styles.a5c3f351.mjs.map │ │ │ │ ├── error-500-styles.6b5b5ff2.mjs.map │ │ │ │ ├── detail-94799639.mjs.map │ │ │ │ ├── island-renderer-d2364c4c.mjs.map │ │ │ │ ├── index-5689abd7.mjs.map │ │ │ │ ├── detail-94799639.mjs │ │ │ │ ├── error-500-d1baf535.mjs.map │ │ │ │ ├── island-renderer-d2364c4c.mjs │ │ │ │ ├── index-5689abd7.mjs │ │ │ │ ├── app.config-a3bb0fb9.mjs │ │ │ │ ├── app.config-a3bb0fb9.mjs.map │ │ │ │ ├── error-500-styles.6b5b5ff2.mjs │ │ │ │ ├── error-component-9ecfbe97.mjs │ │ │ │ ├── error-404-59cb290a.mjs.map │ │ │ │ ├── error-component-9ecfbe97.mjs.map │ │ │ │ ├── error-500-d1baf535.mjs │ │ │ │ ├── error-404-styles.a5c3f351.mjs │ │ │ │ ├── error-404-59cb290a.mjs │ │ │ │ ├── nuxt-link-58ca22a8.mjs.map │ │ │ │ └── nuxt-link-58ca22a8.mjs │ │ │ ├── styles.mjs.map │ │ │ ├── styles.mjs │ │ │ ├── client.manifest.mjs.map │ │ │ └── client.manifest.mjs │ │ ├── error-500.mjs.map │ │ ├── error-500.mjs │ │ ├── handlers │ │ │ ├── renderer.mjs │ │ │ └── renderer.mjs.map │ │ └── nitro │ │ │ └── vercel.mjs.map │ │ ├── index.mjs │ │ └── package.json │ ├── nitro.json │ └── config.json ├── .gitignore ├── store ├── counter.ts └── user.ts ├── server ├── database │ ├── client.ts │ ├── migrations │ │ ├── migration_lock.toml │ │ ├── 20230509035903_init │ │ │ └── migration.sql │ │ ├── 20230509025205_init │ │ │ └── migration.sql │ │ └── 20230404135755_init │ │ │ └── migration.sql │ ├── repositories │ │ ├── userRepository.ts │ │ ├── columnRepository.ts │ │ ├── courseRepository.ts │ │ └── orderRepository.ts │ ├── service │ │ └── token.ts │ ├── test.ts │ ├── schema.prisma │ └── seed.ts └── api │ ├── ordercomplete.post.ts │ ├── order │ └── [id].ts │ ├── indexdata.ts │ ├── purchased-course.ts │ ├── column.ts │ ├── course.ts │ ├── column │ └── [id].ts │ ├── course │ └── [id].ts │ ├── order.post.ts │ ├── userinfo.post.ts │ ├── userinfo.get.ts │ ├── login.post.ts │ ├── register.post.ts │ └── changePwd.post.ts ├── .env.template ├── pages ├── hello.vue ├── usercenter │ ├── buy.vue │ └── info.vue ├── index.vue ├── usercenter.vue ├── order-confirm.vue ├── list │ └── [type].vue ├── order-pay.vue ├── login.vue ├── register.vue └── [type] │ └── detail │ └── [id].vue ├── .vscode └── settings.json ├── deploy.sh ├── composables ├── descrete.ts ├── auth.ts └── request.ts ├── layouts ├── blank.vue └── default.vue ├── tsconfig.json ├── Dockerfile ├── middleware └── auth.ts ├── app.vue ├── test └── index.spec.ts ├── error.vue ├── components ├── LoadingCourseSkeleton.vue ├── MyFooter.vue ├── Menu.vue ├── Catalogue.vue ├── ProdList.vue ├── Loading.vue ├── Tabs.vue ├── Counter.vue ├── Prod.vue └── MyHeader.vue ├── .github └── workflows │ └── publish.yml ├── nuxt.config.ts ├── package.json ├── docker-compose.yml └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@antfu" 3 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /types/IResult.ts: -------------------------------------------------------------------------------- 1 | export interface IResult { ok: boolean; data: any } 2 | -------------------------------------------------------------------------------- /public/介绍.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/介绍.jpeg -------------------------------------------------------------------------------- /public/前端工程化.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/前端工程化.png -------------------------------------------------------------------------------- /public/大纲.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/大纲.jpeg -------------------------------------------------------------------------------- /public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/avatar.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/qrcode.jpg -------------------------------------------------------------------------------- /public/Docker部署.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/Docker部署.png -------------------------------------------------------------------------------- /public/Node与服务器端.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/Node与服务器端.jpg -------------------------------------------------------------------------------- /public/React全栈进阶.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/React全栈进阶.png -------------------------------------------------------------------------------- /public/Vue3组件库实战.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/Vue3组件库实战.png -------------------------------------------------------------------------------- /public/Vue源码全家桶.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/Vue源码全家桶.jpeg -------------------------------------------------------------------------------- /public/webpack优化.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/webpack优化.jpeg -------------------------------------------------------------------------------- /public/column-nuxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/column-nuxt.png -------------------------------------------------------------------------------- /public/course-nuxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/public/course-nuxt.png -------------------------------------------------------------------------------- /types/IUser.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id?: number 3 | email: string 4 | name?: string 5 | } 6 | -------------------------------------------------------------------------------- /.vercel/output/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/nuxt-app/HEAD/.vercel/output/static/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /store/counter.ts: -------------------------------------------------------------------------------- 1 | export const useCounter = defineStore('count', { 2 | state: () => ({ 3 | count: 1, 4 | }), 5 | }) 6 | -------------------------------------------------------------------------------- /store/user.ts: -------------------------------------------------------------------------------- 1 | export const useUser = defineStore('user', { 2 | state: () => ({ 3 | userInfo: null, 4 | }), 5 | }) 6 | -------------------------------------------------------------------------------- /server/database/client.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prisma = new PrismaClient() 4 | export default prisma 5 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/index.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.mjs","sources":[],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;;;;"} -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | DATABASE_URL="mysql://root:rootpassword@localhost:3306/ycxt" 2 | NUXT_BASE_URL=https://jsonplaceholder.typicode.com 3 | JSON_SECRET=thisisjsonsecret -------------------------------------------------------------------------------- /pages/hello.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.vercel/output/nitro.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2023-02-24T01:03:34.451Z", 3 | "preset": "vercel", 4 | "commands": { 5 | "preview": "", 6 | "deploy": "" 7 | } 8 | } -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/app.config.e684eb09.js: -------------------------------------------------------------------------------- 1 | import{k as u,A as s}from"./entry.139a88d9.js";function a(e,n){return u()._useHead(e,n)}const o={};s(o);export{a as u}; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": false, 3 | "editor.formatOnSave": false, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | } 7 | } -------------------------------------------------------------------------------- /server/database/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/.vc-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtime": "nodejs16.x", 3 | "handler": "index.mjs", 4 | "launcherType": "Nodejs", 5 | "shouldAddHelpers": false 6 | } -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/rollup/_virtual_head-static.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"_virtual_head-static.mjs","sources":[],"sourcesContent":null,"names":[],"mappings":";;;;"} -------------------------------------------------------------------------------- /server/database/migrations/20230509035903_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `Column` ADD COLUMN `oPrice` DECIMAL(65, 30) NULL, 3 | ADD COLUMN `price` DECIMAL(65, 30) NULL; 4 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | echo Deploy Project 2 | # docker-compose up -d --force-recreate --build 3 | 4 | # 获取最新版代码 5 | git pull 6 | 7 | # 强制重新编译容器 8 | docker-compose down 9 | docker-compose up -d --force-recreate --build -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/detail.37523c12.js: -------------------------------------------------------------------------------- 1 | import{a as e,o as t,b as a}from"./entry.139a88d9.js";const c={};function n(o,r){return t(),a("h1",null,"Detail Page")}const l=e(c,[["render",n]]);export{l as default}; 2 | -------------------------------------------------------------------------------- /server/database/migrations/20230509025205_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE `Column` ADD COLUMN `url` VARCHAR(191) NULL; 3 | 4 | -- AlterTable 5 | ALTER TABLE `Course` ADD COLUMN `url` VARCHAR(191) NULL; 6 | -------------------------------------------------------------------------------- /composables/descrete.ts: -------------------------------------------------------------------------------- 1 | import { createDiscreteApi } from 'naive-ui' 2 | 3 | export const { message, notification, dialog, loadingBar } = createDiscreteApi( 4 | ['message', 'dialog', 'notification', 'loadingBar'], 5 | ) 6 | -------------------------------------------------------------------------------- /layouts/blank.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "compilerOptions": { 5 | "noImplicitAny": false 6 | }, 7 | "ts-node": { 8 | "compilerOptions": { 9 | "module":"CommonJS" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #Dockerfile 2 | #制定node镜像的版本 3 | FROM node:18-alpine 4 | #移动当前目录下面的文件到app目录下 5 | ADD . /app/ 6 | #进入到app目录下面,类似cd 7 | WORKDIR /app 8 | #安装依赖 9 | RUN npm config set registry https://registry.npm.taobao.org/ && \ 10 | npm i 11 | #对外暴露的端口 12 | EXPOSE 3000 13 | #程序启动脚本 14 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /middleware/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware((to, from) => { 2 | const token = useCookie('token') 3 | const route = useRoute() 4 | 5 | // 未登录重定向到登录页 6 | if (!token.value) { 7 | if (process.client) 8 | message.error('请先登录') 9 | 10 | return navigateTo(`/login?from=${route.fullPath}`) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/index.d321cf39.js: -------------------------------------------------------------------------------- 1 | import{_ as t}from"./nuxt-link.dd8747fa.js";import{a,o,b as n,e as c,w as s,F as _,f as r,h as d}from"./entry.139a88d9.js";const l={},i=r("h1",null,"Index Page",-1);function f(m,x){const e=t;return o(),n(_,null,[i,c(e,{to:"/detail"},{default:s(()=>[d("Detail Page")]),_:1})],64)}const h=a(l,[["render",f]]);export{h as default}; 2 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/rollup/_virtual_head-static.mjs: -------------------------------------------------------------------------------- 1 | const _virtual__headStatic = {"headTags":"\n","bodyTags":"","bodyTagsOpen":"","htmlAttrs":"","bodyAttrs":""}; 2 | 3 | export { _virtual__headStatic as default }; 4 | //# sourceMappingURL=_virtual_head-static.mjs.map 5 | -------------------------------------------------------------------------------- /composables/auth.ts: -------------------------------------------------------------------------------- 1 | export function logout() { 2 | // 清除状态 3 | const store = useUser() 4 | store.userInfo = null 5 | 6 | // 清cookie 7 | const token = useCookie('token') 8 | if (token.value) 9 | token.value = null 10 | 11 | message.success('退出登录成功') 12 | 13 | // 回到首页 14 | const route = useRoute() 15 | if (route.path !== '/') 16 | navigateTo('/') 17 | } 18 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest' 2 | import { $fetch, setup } from '@nuxt/test-utils' 3 | 4 | describe('My test', async () => { 5 | await setup({ 6 | // test context options 7 | }) 8 | 9 | test('index page should be work', async () => { 10 | const html = await $fetch('/') 11 | expect(html).toMatch('

Index Page

') 12 | }) 13 | }) -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-404-styles.a5c3f351.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-404-styles.a5c3f351.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-404-styles-1.mjs-6ef6e240.js","/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-404-styles.a5c3f351.mjs"],"sourcesContent":null,"names":["style_0"],"mappings":"AAAA,MAAM,wDAA2D,GAAA,ukHAAA;;ACCjE,gCAAe,CAACA,wDAAO;;;;"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-500-styles.6b5b5ff2.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-500-styles.6b5b5ff2.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-500-styles-1.mjs-0a86f27a.js","/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-500-styles.6b5b5ff2.mjs"],"sourcesContent":null,"names":["style_0"],"mappings":"AAAA,MAAM,wDAA2D,GAAA,s6DAAA;;ACCjE,gCAAe,CAACA,wDAAO;;;;"} -------------------------------------------------------------------------------- /server/api/ordercomplete.post.ts: -------------------------------------------------------------------------------- 1 | import { updateOrder } from '../database/repositories/orderRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | const body = await readBody(e) 5 | 6 | try { 7 | await updateOrder(Number(body.id), { status: body.status }) 8 | return { ok: true } 9 | } 10 | catch (error) { 11 | return sendError(e, createError('订单状态更新失败')) 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/styles.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"styles.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/styles.mjs"],"sourcesContent":null,"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,GAAE;AAChD,eAAe;AACf,EAAE,8DAA8D,EAAE,MAAM,OAAO,uCAAuC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;AAC5I,EAAE,8DAA8D,EAAE,MAAM,OAAO,uCAAuC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC;AAC5I;;;;"} -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /components/LoadingCourseSkeleton.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /server/api/order/[id].ts: -------------------------------------------------------------------------------- 1 | import { getOrderById } from '~/server/database/repositories/orderRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | // 获取订单id 5 | const id = e.context.params?.id ? parseInt(e.context.params.id) : undefined 6 | if (!id) { 7 | return sendError(e, createError({ 8 | statusCode: 400, 9 | statusMessage: '缺少订单id', 10 | })) 11 | } 12 | const order = await getOrderById(id) 13 | return { ok: true, data: order } 14 | }) 15 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/styles.mjs: -------------------------------------------------------------------------------- 1 | const interopDefault = r => r.default || r || []; 2 | const styles = { 3 | "node_modules/@nuxt/ui-templates/dist/templates/error-404.vue": () => import('./_nuxt/error-404-styles.a5c3f351.mjs').then(interopDefault), 4 | "node_modules/@nuxt/ui-templates/dist/templates/error-500.vue": () => import('./_nuxt/error-500-styles.6b5b5ff2.mjs').then(interopDefault) 5 | }; 6 | 7 | export { styles as default }; 8 | //# sourceMappingURL=styles.mjs.map 9 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/index.mjs: -------------------------------------------------------------------------------- 1 | globalThis._importMeta_={url:import.meta.url,env:process.env};import 'node-fetch-native/polyfill'; 2 | import 'h3'; 3 | import 'ufo'; 4 | export { v as default } from './chunks/nitro/vercel.mjs'; 5 | import 'ofetch'; 6 | import 'destr'; 7 | import 'unenv/runtime/fetch/index'; 8 | import 'hookable'; 9 | import 'scule'; 10 | import 'ohash'; 11 | import 'unstorage'; 12 | import 'defu'; 13 | import 'radix3'; 14 | //# sourceMappingURL=index.mjs.map 15 | -------------------------------------------------------------------------------- /components/MyFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /server/api/indexdata.ts: -------------------------------------------------------------------------------- 1 | import { getNewColumns } from '../database/repositories/columnRepository' 2 | import { getNewCourses } from '../database/repositories/courseRepository' 3 | 4 | export default defineEventHandler(async (e) => { 5 | try { 6 | const columns = await getNewColumns() 7 | const courses = await getNewCourses() 8 | 9 | return { ok: true, data: { columns, courses } } 10 | } 11 | catch (error) { 12 | return sendError(e, createError('获取数据失败')) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /components/Menu.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | -------------------------------------------------------------------------------- /server/api/purchased-course.ts: -------------------------------------------------------------------------------- 1 | import { isNuxtError } from 'nuxt/app' 2 | import { getCoursesByUser } from '../database/repositories/orderRepository' 3 | import { getTokenInfo } from '../database/service/token' 4 | export default defineEventHandler(async (e) => { 5 | try { 6 | const token = getTokenInfo(e) 7 | 8 | if (isNuxtError(token)) 9 | return token 10 | 11 | const courses = await getCoursesByUser(token.id) 12 | 13 | return { ok: true, data: courses } 14 | } 15 | catch (error) { 16 | return sendError(e, createError('获取数据失败')) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /server/api/column.ts: -------------------------------------------------------------------------------- 1 | import { getColumns } from '../database/repositories/columnRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | try { 5 | // 获取分页信息 6 | const query = getQuery(e) 7 | const page = query.page ? parseInt(query.page as string) : 0 8 | const size = query.size ? parseInt(query.size as string) : 8 9 | // 分页获取课程列表和总条数 10 | const { columns, total } = await getColumns({ page, size }) 11 | 12 | return { ok: true, data: { list: columns, total } } 13 | } 14 | catch (error) { 15 | return sendError(e, createError('获取数据失败')) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /server/api/course.ts: -------------------------------------------------------------------------------- 1 | import { getCourses } from '../database/repositories/courseRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | try { 5 | // 获取分页信息 6 | const query = getQuery(e) 7 | const page = query.page ? parseInt(query.page as string) : 0 8 | const size = query.size ? parseInt(query.size as string) : 8 9 | // 分页获取课程列表和总条数 10 | const { courses, total } = await getCourses({ page, size }) 11 | 12 | return { ok: true, data: { list: courses, total } } 13 | } 14 | catch (error) { 15 | return sendError(e, createError('获取数据失败')) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Nuxt To HuaweiYun 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | jobs: 7 | PullSource: 8 | runs-on: ubuntu-latest 9 | name: "PullSource" 10 | steps: 11 | - name: Pull source 12 | uses: appleboy/ssh-action@v0.1.6 13 | with: 14 | host: "123.249.115.108" 15 | username: root 16 | key: ${{ secrets.ACCESS_TOKEN }} 17 | script: | 18 | cd /root/source/nuxt-app 19 | git checkout . 20 | docker-compose down 21 | docker-compose up -d --force-recreate --build 22 | -------------------------------------------------------------------------------- /.vercel/output/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "overrides": {}, 4 | "routes": [ 5 | { 6 | "headers": { 7 | "cache-control": "public, max-age=2592000, immutable" 8 | }, 9 | "src": "/_nuxt/.*" 10 | }, 11 | { 12 | "src": "/_nuxt(.*)", 13 | "headers": { 14 | "cache-control": "public,max-age=31536000,immutable" 15 | }, 16 | "continue": true 17 | }, 18 | { 19 | "handle": "filesystem" 20 | }, 21 | { 22 | "src": "/__nuxt_error", 23 | "dest": "/__nitro" 24 | }, 25 | { 26 | "src": "/(.*)", 27 | "dest": "/__nitro" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /components/Catalogue.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /server/database/repositories/userRepository.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '@prisma/client' 2 | import prisma from '~/server/database/client' 3 | 4 | export async function getUserByUsername(username: string): Promise { 5 | const result = await prisma.user.findUnique({ 6 | where: { 7 | username, 8 | }, 9 | }) 10 | return result 11 | } 12 | 13 | export async function createUser(data: User) { 14 | const user = await prisma.user.create({ data }) 15 | return user 16 | } 17 | 18 | export async function updateUser(id, data: Partial) { 19 | const user = await prisma.user.update({ 20 | where: { 21 | id, 22 | }, 23 | data, 24 | }) 25 | return user 26 | } 27 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | imports: { 3 | dirs: ['store'], 4 | }, 5 | modules: [ 6 | '@unocss/nuxt', 7 | '@huntersofbook/naive-ui-nuxt', 8 | [ 9 | '@pinia/nuxt', 10 | { 11 | autoImports: [ 12 | // 自动引入 `defineStore(), storeToRefs()` 13 | 'defineStore', 14 | 'storeToRefs', 15 | ], 16 | }, 17 | ], 18 | ], 19 | unocss: { 20 | uno: true, // enabled `@unocss/preset-uno` 21 | icons: true, // enabled `@unocss/preset-icons` 22 | attributify: true, // enabled `@unocss/preset-attributify`, 23 | // core options 24 | shortcuts: [], 25 | rules: [], 26 | safelist: [], 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/detail-94799639.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"detail-94799639.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/detail-94799639.js"],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAcA,MAAM,YAAY,EAAC,CAAA;AACnB,SAAS,cAAe,CAAA,IAAA,EAAM,KAAO,EAAA,OAAA,EAAS,MAAQ,EAAA;AACpD,EAAM,KAAA,CAAA,CAAA,GAAA,EAAM,cAAe,CAAA,MAAM,CAAoB,CAAA,iBAAA,CAAA,CAAA,CAAA;AACvD,CAAA;AACA,MAAM,aAAa,SAAU,CAAA,KAAA,CAAA;AAC7B,SAAU,CAAA,KAAA,GAAQ,CAAC,KAAA,EAAO,GAAQ,KAAA;AAChC,EAAA,MAAM,aAAa,aAAc,EAAA,CAAA;AACjC,EAAC,CAAA,UAAA,CAAW,YAAY,UAAW,CAAA,OAAA,uBAA8B,GAAI,EAAA,CAAA,EAAI,IAAI,kBAAkB,CAAA,CAAA;AAC/F,EAAA,OAAO,UAAa,GAAA,UAAA,CAAW,KAAO,EAAA,GAAG,CAAI,GAAA,KAAA,CAAA,CAAA;AAC/C,CAAA,CAAA;AACM,MAAA,MAAA,+BAAqC,SAAW,EAAA,CAAC,CAAC,WAAa,EAAA,cAAc,CAAC,CAAC;;;;"} -------------------------------------------------------------------------------- /server/api/column/[id].ts: -------------------------------------------------------------------------------- 1 | import { getColumnById, getColumns } from '~/server/database/repositories/columnRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | const id = e.context.params?.id ? parseInt(e.context.params.id) : undefined 5 | if (!id) 6 | return sendError(e, createError({ statusCode: 400, statusMessage: '参数错误' })) 7 | try { 8 | const column = await getColumnById(id) 9 | if (!column) 10 | return sendError(e, createError({ statusCode: 404, statusMessage: '没有对应专栏' })) 11 | 12 | const { columns: recommend } = await getColumns({ page: 1, size: 2 }) 13 | 14 | return { ok: true, data: { item: column, recommend } } 15 | } 16 | catch (error) { 17 | return sendError(e, createError({ statusCode: 500, statusMessage: '服务器错误' })) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /server/api/course/[id].ts: -------------------------------------------------------------------------------- 1 | import { getCourseById, getCourses } from '~/server/database/repositories/courseRepository' 2 | 3 | export default defineEventHandler(async (e) => { 4 | const id = e.context.params?.id ? parseInt(e.context.params.id) : undefined 5 | if (!id) 6 | return sendError(e, createError({ statusCode: 400, statusMessage: '参数错误' })) 7 | try { 8 | const course = await getCourseById(id) 9 | if (!course) 10 | return sendError(e, createError({ statusCode: 404, statusMessage: '没有对应课程' })) 11 | 12 | const { courses: recommend } = await getCourses({ page: 1, size: 2 }) 13 | 14 | return { ok: true, data: { item: course, recommend } } 15 | } 16 | catch (error) { 17 | return sendError(e, createError({ statusCode: 500, statusMessage: '服务器错误' })) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /server/database/service/token.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export function getTokenInfo(e) { 4 | let info 5 | 6 | const token = getCookie(e, 'token') 7 | 8 | // 令牌不存在 9 | if (!token) { 10 | return createError({ 11 | statusCode: 401, 12 | statusMessage: 'token不存在!', 13 | }) 14 | } 15 | 16 | try { 17 | // 解析token 18 | info = jwt.verify(token, process.env.JSON_SECRET!) 19 | const currentTime = Date.now() / 1000 20 | 21 | if (info.exp < currentTime) { 22 | return createError({ 23 | statusCode: 401, 24 | statusMessage: 'token过期!', 25 | }) 26 | } 27 | 28 | return info 29 | } 30 | catch (error) { 31 | return createError({ 32 | statusCode: 401, 33 | statusMessage: 'token不合法!', 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /components/ProdList.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | -------------------------------------------------------------------------------- /server/api/order.post.ts: -------------------------------------------------------------------------------- 1 | import type { Order } from '@prisma/client' 2 | import { OrderStatus } from '@prisma/client' 3 | import { isNuxtError } from 'nuxt/app' 4 | import { getTokenInfo } from '../database/service/token' 5 | import { createOrder } from '../database/repositories/orderRepository' 6 | 7 | export default defineEventHandler(async (e) => { 8 | // 课程id 9 | const { courseId } = await readBody(e) 10 | 11 | // 用户id 12 | const result = getTokenInfo(e) 13 | if (isNuxtError(result)) 14 | return sendError(e, result) 15 | 16 | // 构建订单实体 17 | const order = { 18 | courseId: Number(courseId), 19 | userId: result.id, 20 | createdAt: new Date(), 21 | status: OrderStatus.WAIT_CONFIRM, 22 | } as Order 23 | 24 | const o = await createOrder(order) 25 | 26 | return { ok: true, data: { orderId: o.id } } 27 | }) 28 | -------------------------------------------------------------------------------- /server/database/repositories/columnRepository.ts: -------------------------------------------------------------------------------- 1 | import type { Column } from '@prisma/client' 2 | import prisma from '~/server/database/client' 3 | 4 | export async function getNewColumns(): Promise { 5 | const result = await prisma.column.findMany({ 6 | orderBy: { id: 'desc' }, 7 | take: 4, 8 | }) 9 | return result 10 | } 11 | 12 | export async function getColumns({ page, size }): Promise<{ columns: Column[] | null; total: number }> { 13 | const [columns, total] = await Promise.all([ 14 | prisma.column.findMany({ 15 | orderBy: { id: 'desc' }, 16 | skip: page * size, 17 | take: size, 18 | }), 19 | prisma.column.count(), 20 | ]) 21 | return { columns, total } 22 | } 23 | 24 | export async function getColumnById(id: number): Promise { 25 | const result = await prisma.column.findFirst({ 26 | where: { 27 | id, 28 | }, 29 | }) 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /components/Loading.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | -------------------------------------------------------------------------------- /components/Tabs.vue: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 31 | 32 | 38 | -------------------------------------------------------------------------------- /server/database/repositories/courseRepository.ts: -------------------------------------------------------------------------------- 1 | import type { Course } from '@prisma/client' 2 | import prisma from '~/server/database/client' 3 | 4 | export async function getNewCourses(): Promise { 5 | const result = await prisma.course.findMany({ 6 | orderBy: { id: 'desc' }, 7 | take: 4, 8 | }) 9 | return result 10 | } 11 | 12 | export async function getCourses({ page, size }): Promise<{ courses: Course[] | null; total: number }> { 13 | const [courses, total] = await Promise.all([ 14 | prisma.course.findMany({ 15 | orderBy: { id: 'desc' }, 16 | skip: page * size, 17 | take: size, 18 | }), 19 | prisma.course.count(), 20 | ]) 21 | return { courses, total } 22 | } 23 | 24 | export async function getCourseById(id: number): Promise { 25 | const result = await prisma.course.findFirst({ 26 | where: { 27 | id, 28 | }, 29 | include: { Catalogue: true }, 30 | }) 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/error-500.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-500.mjs","sources":["/Users/57code/nuxt-app/node_modules/@nuxt/ui-templates/dist/templates/error-500.mjs"],"sourcesContent":null,"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,cAAc,CAAC,aAAa,CAAC,uCAAuC,EAAC;AACvJ,MAAM,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,EAAE;AACvC,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;AAClB,GAAG,IAAI,oCAAoC;AAC3C,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,UAAU,EAAE,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC;AACpD,KAAK;AACL,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,aAAa,EAAE,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC;AACvD,KAAK;AACL,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,OAAO,EAAE,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC;AACjD,i1GAAi1G;AACj1G,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,UAAU,EAAE,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC;AACpD,kFAAkF;AAClF,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC;AACrD,0BAA0B,CAAC;AAC3B,OAAO,GAAG;AACV,EAAC;AACD,MAAM,SAAS,GAAG,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,GAAG,SAAS,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAC;AACxE,MAAC,QAAQ,GAAG;;;;"} -------------------------------------------------------------------------------- /components/Counter.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 47 | -------------------------------------------------------------------------------- /server/database/test.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | const prisma = new PrismaClient() 4 | 5 | async function main() { 6 | // 插入一条数据 7 | // await prisma.user.create({ 8 | // data: { 9 | // name: '村长', 10 | // email: 'yt0379@qq.com', 11 | // posts: { 12 | // create: { 13 | // title: '10分钟速通下一代ORM解决方案:Prisma', 14 | // }, 15 | // }, 16 | // }, 17 | // }) 18 | 19 | const post = await prisma.post.update({ 20 | where: { id: 1 }, 21 | data: { published: true }, 22 | }) 23 | 24 | // eslint-disable-next-line no-console 25 | console.log(post) 26 | 27 | // 查询所有用户 28 | const allUsers = await prisma.user.findMany({ 29 | include: { 30 | posts: true, 31 | }, 32 | }) 33 | 34 | // console.log(allUsers) 35 | // eslint-disable-next-line no-console 36 | console.dir(allUsers, { depth: null }) 37 | } 38 | 39 | main() 40 | .then(async () => { 41 | await prisma.$disconnect() 42 | }) 43 | .catch(async (e) => { 44 | console.error(e) 45 | await prisma.$disconnect() 46 | process.exit(1) 47 | }) 48 | -------------------------------------------------------------------------------- /pages/usercenter/buy.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /server/api/userinfo.post.ts: -------------------------------------------------------------------------------- 1 | import { isNuxtError } from 'nuxt/app' 2 | import { updateUser } from '../database/repositories/userRepository' 3 | import { getTokenInfo } from '../database/service/token' 4 | 5 | export default defineEventHandler(async (e) => { 6 | // 验证权限 7 | const token = getTokenInfo(e) 8 | 9 | if (isNuxtError(token)) { 10 | return sendError(e, createError({ 11 | statusCode: 401, 12 | statusMessage: 'token不合法!', 13 | })) 14 | } 15 | 16 | try { 17 | // 获取更新数据 18 | const body = await readBody(e) 19 | 20 | if (!body || body.username || body.password) { 21 | let statusMessage 22 | if (!body) 23 | statusMessage = '参数不存在' 24 | else if (body.username) 25 | statusMessage = '用户名不能修改' 26 | else 27 | statusMessage = '请使用修改密码接口' 28 | return sendError(e, createError({ 29 | statusCode: 400, 30 | statusMessage, 31 | })) 32 | } 33 | 34 | const user = await updateUser(token.id, body) 35 | return { ok: true, data: user } 36 | } 37 | catch (error) { 38 | console.error(error) 39 | return sendError(e, createError('更新用户信息失败!')) 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/island-renderer-d2364c4c.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"island-renderer-d2364c4c.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/island-renderer-d2364c4c.js"],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAcA,MAAM,qBAAqB,EAAC,CAAA;AAC5B,MAAM,gBAAA,0BAA0C,MAAO,CAAA;AAAA,EACrD,SAAW,EAAA,IAAA;AAAA,EACX,OAAS,EAAA,kBAAA;AACX,CAAC,CAAA,CAAA;AACD,MAAM,iCAAiD,eAAA,CAAA;AAAA,EACrD,KAAO,EAAA;AAAA,IACL,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,MAAA;AAAA,MACN,QAAU,EAAA,IAAA;AAAA,KACZ;AAAA,GACF;AAAA,EACA,MAAM,MAAM,KAAO,EAAA;AACjB,IAAI,IAAA,EAAA,CAAA;AACJ,IAAA,MAAM,SAAY,GAAA,gBAAA,CAAiB,KAAM,CAAA,OAAA,CAAQ,IAAI,CAAA,CAAA;AACrD,IAAA,IAAI,CAAC,SAAW,EAAA;AACd,MAAA,MAAM,WAAY,CAAA;AAAA,QAChB,UAAY,EAAA,GAAA;AAAA,QACZ,aAAe,EAAA,CAAA,4BAAA,EAA+B,IAAK,CAAA,SAAA,CAAU,SAAS,CAAA,CAAA,CAAA;AAAA,OACvE,CAAA,CAAA;AAAA,KACH;AACA,IAAI,IAAA,OAAO,cAAc,QAAU,EAAA;AACjC,MAAA,OAAA,CAAQ,KAAK,SAAU,CAAA,aAAA,KAAkB,OAAO,KAAS,CAAA,GAAA,EAAA,CAAG,KAAK,SAAS,CAAA,CAAA,CAAA;AAAA,KAC5E;AACA,IAAA,OAAO,MAAM;AAAA,MACX,WAAY,CAAA,QAAA,EAAU,EAAE,EAAA,EAAI,eAAiB,EAAA,CAAC,CAAE,CAAA,SAAA,IAAa,MAAQ,EAAA,KAAA,CAAM,OAAQ,CAAA,KAAK,CAAC,CAAC,CAAA;AAAA,KAC5F,CAAA;AAAA,GACF;AACF,CAAC;;;;"} -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/error-component.3b96486c.js: -------------------------------------------------------------------------------- 1 | import{o as l,c as m,n as E,g as f,u as s,d as n,_ as o}from"./entry.139a88d9.js";const g={__name:"nuxt-error-page",props:{error:Object},setup(c){const{error:t}=c;(t.stack||"").split(` 2 | `).splice(1).map(e=>({text:e.replace("webpack:/","").replace(".vue",".js").trim(),internal:e.includes("node_modules")&&!e.includes(".cache")||e.includes("internal")||e.includes("new Promise")})).map(e=>`${e.text}`).join(` 3 | `);const r=Number(t.statusCode||500),a=r===404,u=t.statusMessage??(a?"Page Not Found":"Internal Server Error"),i=t.message||t.toString(),p=void 0,_=a?n(()=>o(()=>import("./error-404.7326de30.js"),["./error-404.7326de30.js","./nuxt-link.dd8747fa.js","./entry.139a88d9.js","./app.config.e684eb09.js","./error-404.23f2309d.css"],import.meta.url).then(e=>e.default||e)):n(()=>o(()=>import("./error-500.5a01185b.js"),["./error-500.5a01185b.js","./entry.139a88d9.js","./app.config.e684eb09.js","./error-500.aa16ed4d.css"],import.meta.url).then(e=>e.default||e));return(e,d)=>(l(),m(s(_),E(f({statusCode:s(r),statusMessage:s(u),description:s(i),stack:s(p)})),null,16))}},x=g;export{x as default}; 4 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/index-5689abd7.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index-5689abd7.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/index-5689abd7.js"],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAeA,MAAM,YAAY,EAAC,CAAA;AACnB,SAAS,cAAe,CAAA,IAAA,EAAM,KAAO,EAAA,OAAA,EAAS,MAAQ,EAAA;AACpD,EAAA,MAAM,mBAAsB,GAAA,kBAAA,CAAA;AAC5B,EAAA,KAAA,CAAM,CAA6B,2BAAA,CAAA,CAAA,CAAA;AACnC,EAAA,KAAA,CAAM,kBAAmB,CAAA,mBAAA,EAAqB,EAAE,EAAA,EAAI,WAAa,EAAA;AAAA,IAC/D,SAAS,OAAQ,CAAA,CAAC,CAAG,EAAA,MAAA,EAAQ,UAAU,QAAa,KAAA;AAClD,MAAA,IAAI,MAAQ,EAAA;AACV,QAAA,MAAA,CAAO,CAAa,WAAA,CAAA,CAAA,CAAA;AAAA,OACf,MAAA;AACL,QAAO,OAAA;AAAA,UACL,gBAAgB,aAAa,CAAA;AAAA,SAC/B,CAAA;AAAA,OACF;AAAA,KACD,CAAA;AAAA,IACD,CAAG,EAAA,CAAA;AAAA,GACL,EAAG,OAAO,CAAC,CAAA,CAAA;AACX,EAAA,KAAA,CAAM,CAAU,QAAA,CAAA,CAAA,CAAA;AAClB,CAAA;AACA,MAAM,aAAa,SAAU,CAAA,KAAA,CAAA;AAC7B,SAAU,CAAA,KAAA,GAAQ,CAAC,KAAA,EAAO,GAAQ,KAAA;AAChC,EAAA,MAAM,aAAa,aAAc,EAAA,CAAA;AACjC,EAAC,CAAA,UAAA,CAAW,YAAY,UAAW,CAAA,OAAA,uBAA8B,GAAI,EAAA,CAAA,EAAI,IAAI,iBAAiB,CAAA,CAAA;AAC9F,EAAA,OAAO,UAAa,GAAA,UAAA,CAAW,KAAO,EAAA,GAAG,CAAI,GAAA,KAAA,CAAA,CAAA;AAC/C,CAAA,CAAA;AACM,MAAA,KAAA,+BAAoC,SAAW,EAAA,CAAC,CAAC,WAAa,EAAA,cAAc,CAAC,CAAC;;;;"} -------------------------------------------------------------------------------- /server/api/userinfo.get.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import { getUserByUsername } from '../database/repositories/userRepository' 3 | 4 | export default defineEventHandler(async (e) => { 5 | // 获取令牌 6 | const token = getCookie(e, 'token') 7 | 8 | // 令牌不存在 9 | if (!token) 10 | return { ok: false } 11 | 12 | let info 13 | try { 14 | // 解析token 15 | info = jwt.verify(token, process.env.JSON_SECRET!) 16 | const currentTime = Date.now() / 1000 17 | 18 | if (info.exp < currentTime) { 19 | return sendError(e, createError({ 20 | statusCode: 401, 21 | statusMessage: 'token过期!', 22 | })) 23 | } 24 | } 25 | catch (error) { 26 | return sendError(e, createError({ 27 | statusCode: 401, 28 | statusMessage: 'token不合法!', 29 | })) 30 | } 31 | 32 | try { 33 | const user = await getUserByUsername(info.username) 34 | 35 | // 用户不存在 36 | if (!user) { 37 | return sendError(e, createError({ 38 | statusCode: 401, 39 | statusMessage: '用户不存在!', 40 | })) 41 | } 42 | return { ok: true, data: user } 43 | } 44 | catch (error) { 45 | console.error(error) 46 | return sendError(e, createError('获取用户信息失败!')) 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /server/api/login.post.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | import jwt from 'jsonwebtoken' 3 | import type { User } from '@prisma/client' 4 | import { getUserByUsername } from '../database/repositories/userRepository' 5 | 6 | export default defineEventHandler(async (e) => { 7 | const { username, password } = await readBody(e) 8 | 9 | // 校验... 10 | 11 | try { 12 | // 获取用户 13 | const user = await getUserByUsername(username) 14 | 15 | if (!user) { 16 | return sendError(e, createError({ 17 | statusCode: 401, 18 | statusMessage: '用户错误!', 19 | })) 20 | } 21 | 22 | // 校验密码 23 | const result = await bcrypt.compare(password, user.password) 24 | 25 | if (!result) { 26 | return sendError(e, createError({ 27 | statusCode: 401, 28 | statusMessage: '密码错误!', 29 | })) 30 | } 31 | 32 | // 写入cookie 33 | const secret = process.env.JSON_SECRET as string 34 | const token = jwt.sign({ username: user.username, id: user.id }, secret, { expiresIn: '24h' }) 35 | setCookie(e, 'token', token, { maxAge: 24 * 3600 }) 36 | 37 | return { ok: true, data: user } 38 | } 39 | catch (error) { 40 | console.error(error) 41 | return sendError(e, createError('登录失败!')) 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/detail-94799639.mjs: -------------------------------------------------------------------------------- 1 | import { ssrRenderAttrs } from 'vue/server-renderer'; 2 | import { useSSRContext } from 'vue'; 3 | import { _ as _export_sfc } from '../server.mjs'; 4 | import 'ofetch'; 5 | import 'hookable'; 6 | import 'unctx'; 7 | import '@unhead/vue'; 8 | import '@unhead/dom'; 9 | import '@unhead/ssr'; 10 | import 'vue-router'; 11 | import 'h3'; 12 | import 'ufo'; 13 | import 'defu'; 14 | import '../../nitro/vercel.mjs'; 15 | import 'node-fetch-native/polyfill'; 16 | import 'destr'; 17 | import 'unenv/runtime/fetch/index'; 18 | import 'scule'; 19 | import 'ohash'; 20 | import 'unstorage'; 21 | import 'radix3'; 22 | 23 | const _sfc_main = {}; 24 | function _sfc_ssrRender(_ctx, _push, _parent, _attrs) { 25 | _push(`Detail Page`); 26 | } 27 | const _sfc_setup = _sfc_main.setup; 28 | _sfc_main.setup = (props, ctx) => { 29 | const ssrContext = useSSRContext(); 30 | (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/detail.vue"); 31 | return _sfc_setup ? _sfc_setup(props, ctx) : void 0; 32 | }; 33 | const detail = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender]]); 34 | 35 | export { detail as default }; 36 | //# sourceMappingURL=detail-94799639.mjs.map 37 | -------------------------------------------------------------------------------- /server/api/register.post.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | import jwt from 'jsonwebtoken' 3 | import type { User } from '@prisma/client' 4 | import { createUser, getUserByUsername } from '../database/repositories/userRepository' 5 | 6 | export default defineEventHandler(async (e) => { 7 | try { 8 | // 用户传递的参数 9 | const data = await readBody(e) 10 | const { username, password } = data 11 | // 校验... 12 | 13 | // 获取用户,存在同名用户 14 | const user = await getUserByUsername(username) 15 | 16 | if (user) { 17 | return sendError(e, createError({ 18 | statusCode: 400, 19 | statusMessage: '用户名已存在!', 20 | })) 21 | } 22 | 23 | // 加密 24 | const salt = await bcrypt.genSalt(10) 25 | const hash = await bcrypt.hash(password, salt) 26 | data.password = hash 27 | 28 | // 入库 29 | const result = await createUser(data) 30 | 31 | // 生成token,写入cookie 32 | const secret = process.env.JSON_SECRET as string 33 | const token = jwt.sign({ username: result.username, id: result.id }, secret, { expiresIn: '24h' }) 34 | setCookie(e, 'token', token, { maxAge: 24 * 3600 }) 35 | 36 | return { ok: true, data: result } 37 | } 38 | catch (error) { 39 | console.error(error) 40 | return sendError(e, createError('注册失败!')) 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "prisma": { 4 | "seed": "ts-node server/database/seed.ts" 5 | }, 6 | "scripts": { 7 | "start": "nuxt start", 8 | "build": "nuxt build", 9 | "dev": "nuxt dev", 10 | "generate": "nuxt generate", 11 | "preview": "nuxt preview", 12 | "postinstall": "nuxt prepare", 13 | "lint": "eslint .", 14 | "lint:fix": "eslint . --fix", 15 | "test:unit": "vitest", 16 | "migrate": "npx prisma migrate dev --name init --schema server/database/schema.prisma", 17 | "seed": "npx prisma db seed", 18 | "clear":"npx prisma migrate reset --schema server/database/schema.prisma" 19 | 20 | }, 21 | "devDependencies": { 22 | "@antfu/eslint-config": "^0.36.0", 23 | "@huntersofbook/naive-ui-nuxt": "^0.7.1", 24 | "@iconify-json/vscode-icons": "^1.1.22", 25 | "@nuxt/test-utils": "^3.2.3", 26 | "@unocss/nuxt": "^0.50.4", 27 | "eslint": "^8.36.0", 28 | "nuxt": "^3.3.3", 29 | "prisma": "^4.11.0", 30 | "ts-node": "^10.9.1", 31 | "typescript": "^4.9.5", 32 | "vitest": "^0.29.2" 33 | }, 34 | "dependencies": { 35 | "@pinia/nuxt": "^0.4.7", 36 | "@prisma/client": "^4.11.0", 37 | "@types/bcryptjs": "^2.4.2", 38 | "@types/jsonwebtoken": "^9.0.1", 39 | "bcryptjs": "^2.4.3", 40 | "jsonwebtoken": "^9.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pages/usercenter.vue: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nitro-output", 3 | "version": "0.0.0", 4 | "private": true, 5 | "bundledDependencies": { 6 | "source-map-support": "0.5.21", 7 | "vue": "3.2.47", 8 | "node-fetch-native": "1.0.2", 9 | "h3": "1.5.0", 10 | "ofetch": "1.0.1", 11 | "ufo": "1.1.0", 12 | "destr": "1.2.2", 13 | "radix3": "1.0.0", 14 | "hookable": "5.4.2", 15 | "defu": "6.1.2", 16 | "unstorage": "1.1.5", 17 | "scule": "1.0.0", 18 | "ohash": "1.0.0", 19 | "vue-bundle-renderer": "1.0.2", 20 | "unctx": "2.1.2", 21 | "vue-router": "4.1.6", 22 | "unenv": "1.2.0", 23 | "@unhead/dom": "1.0.22", 24 | "@unhead/vue": "1.0.22", 25 | "@unhead/ssr": "1.0.22", 26 | "cookie-es": "0.5.0", 27 | "uncrypto": "0.1.2", 28 | "iron-webcrypto": "0.5.0", 29 | "@unhead/shared": "1.0.22", 30 | "unhead": "1.0.22", 31 | "@vue/server-renderer": "3.2.47", 32 | "source-map": "0.6.1", 33 | "buffer-from": "1.1.2", 34 | "@vue/devtools-api": "6.5.0", 35 | "@vue/compiler-dom": "3.2.47", 36 | "@vue/runtime-dom": "3.2.47", 37 | "@vue/shared": "3.2.47", 38 | "@vue/compiler-ssr": "3.2.47", 39 | "@vue/compiler-core": "3.2.47", 40 | "@vue/runtime-core": "3.2.47", 41 | "@vue/reactivity": "3.2.47", 42 | "estree-walker": "2.0.2", 43 | "@babel/parser": "7.21.0" 44 | } 45 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | mysql_db_container: 4 | image: mysql:latest 5 | command: --default-authentication-plugin=mysql_native_password 6 | environment: 7 | MYSQL_ROOT_PASSWORD: rootpassword # root账号密码 8 | ports: 9 | - 3306:3306 10 | volumes: 11 | - mysql_db_data_container:/var/lib/mysql 12 | healthcheck: 13 | test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] 14 | interval: 30s 15 | timeout: 10s 16 | retries: 5 17 | adminer_container: 18 | image: adminer:latest 19 | environment: 20 | ADMINER_DEFAULT_SERVER: mysql_db_container 21 | ports: 22 | - 8080:8080 23 | nuxt_app_container: 24 | container_name: nuxt_app 25 | restart: always 26 | #构建容器 27 | build: 28 | context: . 29 | # 自动输入Y防止造成编译卡死 30 | args: 31 | - "-y" 32 | ports: 33 | - "3004:3000" 34 | environment: 35 | DATABASE_URL: mysql://root:rootpassword@mysql_db_container:3306/ycxt 36 | NUXT_BASE_URL: https://jsonplaceholder.typicode.com 37 | JSON_SECRET: thisisjsonsecret 38 | #command: > 39 | # /bin/sh -c 'npm run migrate && npm run build && npm start' 40 | command: > 41 | /bin/sh -c 'npm run clear && npm run migrate && npm run build && npm start' 42 | depends_on: 43 | mysql_db_container: 44 | condition: service_healthy 45 | volumes: 46 | mysql_db_data_container: 47 | -------------------------------------------------------------------------------- /server/api/changePwd.post.ts: -------------------------------------------------------------------------------- 1 | import { isNuxtError } from 'nuxt/app' 2 | import bcrypt from 'bcryptjs' 3 | import { getUserByUsername, updateUser } from '../database/repositories/userRepository' 4 | import { getTokenInfo } from '../database/service/token' 5 | 6 | export default defineEventHandler(async (e) => { 7 | const token = getTokenInfo(e) 8 | 9 | if (isNuxtError(token)) { 10 | return sendError(e, createError({ 11 | statusCode: 401, 12 | statusMessage: '请先登录!', 13 | })) 14 | } 15 | 16 | try { 17 | // 获取更新数据 18 | const body = await readBody(e) 19 | 20 | if (!body || !body.oldPwd || !body.newPwd) { 21 | return sendError(e, createError({ 22 | statusCode: 400, 23 | statusMessage: '参数错误', 24 | })) 25 | } 26 | 27 | // 验证原密码 28 | const user = await getUserByUsername(token.username) 29 | 30 | // 校验密码 31 | const result = await bcrypt.compare(body.oldPwd, user!.password) 32 | 33 | if (!result) { 34 | return sendError(e, createError({ 35 | statusCode: 400, 36 | statusMessage: '原密码错误!', 37 | })) 38 | } 39 | 40 | // 加密 41 | const salt = await bcrypt.genSalt(10) 42 | const hash = await bcrypt.hash(body.newPwd, salt) 43 | 44 | await updateUser(token.id, { password: hash }) 45 | return { ok: true } 46 | } 47 | catch (error) { 48 | console.error(error) 49 | return sendError(e, createError('更新密码失败!')) 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /components/Prod.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3.0 全栈开发 2 | 3 | 本项目是掘金小册《[Nuxt 3.0全栈开发](https://s.juejin.cn/ds/Sp2b7DR/)》配套代码。 4 | 5 | ## 小册介绍 6 | ![介绍](./public/%E4%BB%8B%E7%BB%8D.jpeg) 7 | 8 | ## 小册内容 9 | 这门课程共分五个模块: 10 | 11 | ![大纲](./public/%E5%A4%A7%E7%BA%B2.jpeg) 12 | 13 | - 模块一,将从渲染模式等基础概念出发,先扭转一些同学的固有思维,补充缺失知识; 14 | - 模块二,结合个人博客案例,深入学习 Nuxt3 核心特性; 15 | - 模块三,解决项目工程化问题,从扩展性、复用性等角度深入了解模块等框架进阶知识; 16 | - 模块四,将为项目实战做准备,给大家讲解全栈知识,包括数据库设计、接口设计和开发,大家会接触并掌握 Apifox、Prisma 等前端比较时髦的新工具; 17 | - 模块五,项目实战,我会带大家开发一个知识分享社区主题的全栈项目,包括了从接口开发,到前端开发,再到优化、部署和持续集成的全流程实战。 18 | 相信学习完本小册,会让你深入掌握 Nuxt3 框架的同时,还能全方位提升自己的知识深度和架构水平。 19 | 20 | ## 你会学到什么? 21 | 本课程同时具备如下优势: 22 | 23 | - 案例驱动教学,核心知识点讲解将会用个人博客案例贯穿,学习基础知识同时掌握实战应用方法; 24 | - 全视频项目演示,每节内容均有配套代码,实战项目从设计到代码实现都有细致视频演示,同时搭配文字稿,满足各种学习需求; 25 | - TS 全栈开发,前后端完全使用 TypeScript 开发,补充后端知识的同时,也是一次 TS 学习实践; 26 | - 工程化实践,Nuxt 项目工程化搭建,多种扩展方法应用实践,自动生成数据库表数据,从开发到自动化部署全流程实战 27 | - 前沿技术栈,Nuxt3 + TS + Vite + Vue3 + NaiveUI + TailwindCSS + Nitro + Node.js + Prisma,给你现代化的开发流程和体验; 28 | 29 | ## 你将获得: 30 | 31 | - 搞清 SSR、SSG、SPA、hybrid 等渲染模式差异和选择; 32 | - 掌握 Nuxt3 核心用法和项目开发技巧; 33 | - 能够在 Nuxt 全栈开发中熟练运用 TypeScript; 34 | - 能完成 Nuxt 项目构建、开发和自动化部署等工程化任务; 35 | - 学会设计和实现接口,学会数据库设计和开发。 36 | 37 | ## 适宜人群 38 | 39 | - 欠缺前端项目经验,想要学习如何快速、高效构建真实完整前端实战项目; 40 | - 没有全栈开发经验,想要对前后端知识加深理解和实战的小伙伴; 41 | - 对前端框架理解不够深入,想要通过项目的实战加以巩固提升; 42 | - 对 TS 掌握不够熟练,希望通过实际开发强化水平的小伙伴们; 43 | - 对于 SSR/SSG 等架构感兴趣,苦于学习资料少、理解门槛较高; 44 | - 对项目性能优化、SEO 等缺乏实践经验的小伙伴 45 | 46 | ## 学习本课程需要哪些基础? 47 | 48 | - Vue 基本语法; 49 | - TypeScript 基础语法; 50 | - Node.js 基础语法。 -------------------------------------------------------------------------------- /pages/order-confirm.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 51 | -------------------------------------------------------------------------------- /server/database/repositories/orderRepository.ts: -------------------------------------------------------------------------------- 1 | import type { Order } from '@prisma/client' 2 | import prisma from '~/server/database/client' 3 | 4 | export async function createOrder(data: Order) { 5 | const order = await prisma.order.create({ data }) 6 | return order 7 | } 8 | 9 | export async function getOrderById(id: number) { 10 | const result = await prisma.order.findUnique({ 11 | where: { 12 | id, 13 | }, 14 | include: { 15 | course: { 16 | select: { 17 | id: true, 18 | title: true, 19 | cover: true, 20 | price: true, 21 | desc: true, 22 | }, 23 | }, 24 | }, 25 | }) 26 | return result 27 | } 28 | 29 | export async function updateOrder(id: number, data: Partial) { 30 | const result = await prisma.order.update({ 31 | where: { 32 | id, 33 | }, 34 | data, 35 | }) 36 | return result 37 | } 38 | 39 | export async function getCoursesByUser(userId: number) { 40 | const orders = await prisma.order.findMany({ 41 | where: { 42 | userId, 43 | }, 44 | include: { 45 | course: { 46 | select: { 47 | id: true, 48 | title: true, 49 | cover: true, 50 | }, 51 | }, 52 | }, 53 | }) 54 | 55 | const courses = orders.flatMap(order => order.course) 56 | const uniqueCourses = courses.filter((course, index, arr) => arr.findIndex(c => c.id === course.id) === index) 57 | return uniqueCourses 58 | } 59 | -------------------------------------------------------------------------------- /pages/list/[type].vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 59 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-500-d1baf535.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-500-d1baf535.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-500-d1baf535.js"],"sourcesContent":null,"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiBA,MAAM,SAAY,GAAA;AAAA,EAChB,MAAQ,EAAA,WAAA;AAAA,EACR,iBAAmB,EAAA,IAAA;AAAA,EACnB,KAAO,EAAA;AAAA,IACL,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,MAAA;AAAA,KACX;AAAA,IACA,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,EAAA;AAAA,KACX;AAAA,IACA,UAAY,EAAA;AAAA,MACV,IAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,GAAA;AAAA,KACX;AAAA,IACA,aAAe,EAAA;AAAA,MACb,IAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,cAAA;AAAA,KACX;AAAA,IACA,WAAa,EAAA;AAAA,MACX,IAAM,EAAA,MAAA;AAAA,MACN,OAAS,EAAA,uCAAA;AAAA,KACX;AAAA,GACF;AAAA,EACA,MAAM,OAAS,EAAA;AACb,IAAA,MAAM,KAAQ,GAAA,OAAA,CAAA;AACd,IAAQ,OAAA,CAAA;AAAA,MACN,OAAO,CAAG,EAAA,KAAA,CAAM,UAAgB,CAAA,GAAA,EAAA,KAAA,CAAM,mBAAmB,KAAM,CAAA,OAAA,CAAA,CAAA;AAAA,MAC/D,QAAQ,EAAC;AAAA,MACT,KAAO,EAAA;AAAA,QACL;AAAA,UACE,QAAU,EAAA,CAAA,uuBAAA,CAAA;AAAA,SACZ;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AACD,IAAA,OAAO,CAAC,IAAA,EAAM,KAAO,EAAA,OAAA,EAAS,MAAW,KAAA;AACvC,MAAA,KAAA,CAAM,OAAO,cAAe,CAAA,UAAA,CAAW,EAAE,KAAA,EAAO,kIAAoI,EAAA,MAAM,CAAC,CAAA,CAAA,6NAAA,EAAiO,eAAe,OAAQ,CAAA,UAAU,oGAAoG,cAAe,CAAA,OAAA,CAAQ,WAAW,CAAmB,CAAA,gBAAA,CAAA,CAAA,CAAA;AAAA,KACxlB,CAAA;AAAA,GACF;AACF,CAAA,CAAA;AACA,MAAM,aAAa,SAAU,CAAA,KAAA,CAAA;AAC7B,SAAU,CAAA,KAAA,GAAQ,CAAC,KAAA,EAAO,GAAQ,KAAA;AAChC,EAAA,MAAM,aAAa,aAAc,EAAA,CAAA;AACjC,EAAC,CAAA,UAAA,CAAW,YAAY,UAAW,CAAA,OAAA,uBAA8B,GAAI,EAAA,CAAA,EAAI,IAAI,8DAA8D,CAAA,CAAA;AAC3I,EAAA,OAAO,UAAa,GAAA,UAAA,CAAW,KAAO,EAAA,GAAG,CAAI,GAAA,KAAA,CAAA,CAAA;AAC/C,CAAA,CAAA;AACM,MAAA,QAAA,+BAAuC,SAAW,EAAA,CAAC,CAAC,WAAa,EAAA,iBAAiB,CAAC,CAAC;;;;"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/island-renderer-d2364c4c.mjs: -------------------------------------------------------------------------------- 1 | import { defineComponent, createBlock, Teleport, h } from 'vue'; 2 | import { c as createError } from '../server.mjs'; 3 | import 'ofetch'; 4 | import 'hookable'; 5 | import 'unctx'; 6 | import '@unhead/vue'; 7 | import '@unhead/dom'; 8 | import '@unhead/ssr'; 9 | import 'vue-router'; 10 | import 'h3'; 11 | import 'ufo'; 12 | import 'vue/server-renderer'; 13 | import 'defu'; 14 | import '../../nitro/vercel.mjs'; 15 | import 'node-fetch-native/polyfill'; 16 | import 'destr'; 17 | import 'unenv/runtime/fetch/index'; 18 | import 'scule'; 19 | import 'ohash'; 20 | import 'unstorage'; 21 | import 'radix3'; 22 | 23 | const components_islands = {}; 24 | const islandComponents = /* @__PURE__ */ Object.freeze({ 25 | __proto__: null, 26 | default: components_islands 27 | }); 28 | const islandRenderer = /* @__PURE__ */ defineComponent({ 29 | props: { 30 | context: { 31 | type: Object, 32 | required: true 33 | } 34 | }, 35 | async setup(props) { 36 | var _a; 37 | const component = islandComponents[props.context.name]; 38 | if (!component) { 39 | throw createError({ 40 | statusCode: 404, 41 | statusMessage: `Island component not found: ${JSON.stringify(component)}` 42 | }); 43 | } 44 | if (typeof component === "object") { 45 | await ((_a = component.__asyncLoader) == null ? void 0 : _a.call(component)); 46 | } 47 | return () => [ 48 | createBlock(Teleport, { to: "nuxt-island" }, [h(component || "span", props.context.props)]) 49 | ]; 50 | } 51 | }); 52 | 53 | export { islandRenderer as default }; 54 | //# sourceMappingURL=island-renderer-d2364c4c.mjs.map 55 | -------------------------------------------------------------------------------- /pages/order-pay.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 56 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/index-5689abd7.mjs: -------------------------------------------------------------------------------- 1 | import { _ as __nuxt_component_0 } from './nuxt-link-58ca22a8.mjs'; 2 | import { withCtx, createTextVNode, useSSRContext } from 'vue'; 3 | import { ssrRenderComponent } from 'vue/server-renderer'; 4 | import { _ as _export_sfc } from '../server.mjs'; 5 | import 'ufo'; 6 | import 'ofetch'; 7 | import 'hookable'; 8 | import 'unctx'; 9 | import '@unhead/vue'; 10 | import '@unhead/dom'; 11 | import '@unhead/ssr'; 12 | import 'vue-router'; 13 | import 'h3'; 14 | import 'defu'; 15 | import '../../nitro/vercel.mjs'; 16 | import 'node-fetch-native/polyfill'; 17 | import 'destr'; 18 | import 'unenv/runtime/fetch/index'; 19 | import 'scule'; 20 | import 'ohash'; 21 | import 'unstorage'; 22 | import 'radix3'; 23 | 24 | const _sfc_main = {}; 25 | function _sfc_ssrRender(_ctx, _push, _parent, _attrs) { 26 | const _component_NuxtLink = __nuxt_component_0; 27 | _push(`

Index Page

`); 28 | _push(ssrRenderComponent(_component_NuxtLink, { to: "/detail" }, { 29 | default: withCtx((_, _push2, _parent2, _scopeId) => { 30 | if (_push2) { 31 | _push2(`Detail Page`); 32 | } else { 33 | return [ 34 | createTextVNode("Detail Page") 35 | ]; 36 | } 37 | }), 38 | _: 1 39 | }, _parent)); 40 | _push(``); 41 | } 42 | const _sfc_setup = _sfc_main.setup; 43 | _sfc_main.setup = (props, ctx) => { 44 | const ssrContext = useSSRContext(); 45 | (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("pages/index.vue"); 46 | return _sfc_setup ? _sfc_setup(props, ctx) : void 0; 47 | }; 48 | const index = /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender]]); 49 | 50 | export { index as default }; 51 | //# sourceMappingURL=index-5689abd7.mjs.map 52 | -------------------------------------------------------------------------------- /pages/usercenter/info.vue: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 68 | -------------------------------------------------------------------------------- /composables/request.ts: -------------------------------------------------------------------------------- 1 | import { merge } from 'lodash' 2 | 3 | type FetchType = typeof $fetch 4 | type ReqType = Parameters[0] 5 | type FetchOptions = Parameters[1] 6 | 7 | export function httpRequest( 8 | method: any, 9 | url: ReqType, 10 | bodyOrParams?: any, 11 | opts?: FetchOptions, 12 | ) { 13 | const token = useCookie('token') 14 | const router = useRouter() 15 | const route = useRoute() 16 | 17 | const defaultOpts = { 18 | method, 19 | // baseURL: '/api', 20 | headers: { token: token.value } as any, 21 | onRequestError() { 22 | message.error('请求出错,请重试!') 23 | }, 24 | onResponseError({ response }) { 25 | switch (response.status) { 26 | case 400: 27 | message.error('参数错误') 28 | break 29 | case 401: 30 | message.error('没有访问权限') 31 | router.push(`/login?callback=${route.path}`) 32 | break 33 | case 403: 34 | message.error('服务器拒绝访问') 35 | break 36 | case 404: 37 | message.error('请求地址错误') 38 | break 39 | case 500: 40 | message.error('服务器故障') 41 | break 42 | default: 43 | message.error('网络连接故障') 44 | break 45 | } 46 | }, 47 | } as FetchOptions 48 | if (defaultOpts) { 49 | if (method === 'post') 50 | defaultOpts.body = bodyOrParams 51 | else 52 | defaultOpts.params = bodyOrParams 53 | } 54 | return $fetch(url, merge(defaultOpts, opts)) 55 | } 56 | 57 | export function httpPost( 58 | request: ReqType, 59 | body?: any, 60 | opts?: FetchOptions, 61 | ) { 62 | return httpRequest('post', request, body, opts) 63 | } 64 | 65 | export function httpGet( 66 | request: ReqType, 67 | opts?: FetchOptions, 68 | ) { 69 | return httpRequest('get', request, null, opts) 70 | } 71 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/app.config-a3bb0fb9.mjs: -------------------------------------------------------------------------------- 1 | import { a as useNuxtApp } from '../server.mjs'; 2 | 3 | function useHead(input, options) { 4 | return useNuxtApp()._useHead(input, options); 5 | } 6 | function isObject(value) { 7 | return value !== null && typeof value === "object"; 8 | } 9 | function _defu(baseObject, defaults, namespace = ".", merger) { 10 | if (!isObject(defaults)) { 11 | return _defu(baseObject, {}, namespace, merger); 12 | } 13 | const object = Object.assign({}, defaults); 14 | for (const key in baseObject) { 15 | if (key === "__proto__" || key === "constructor") { 16 | continue; 17 | } 18 | const value = baseObject[key]; 19 | if (value === null || value === void 0) { 20 | continue; 21 | } 22 | if (merger && merger(object, key, value, namespace)) { 23 | continue; 24 | } 25 | if (Array.isArray(value) && Array.isArray(object[key])) { 26 | object[key] = [...value, ...object[key]]; 27 | } else if (isObject(value) && isObject(object[key])) { 28 | object[key] = _defu( 29 | value, 30 | object[key], 31 | (namespace ? `${namespace}.` : "") + key.toString(), 32 | merger 33 | ); 34 | } else { 35 | object[key] = value; 36 | } 37 | } 38 | return object; 39 | } 40 | function createDefu(merger) { 41 | return (...arguments_) => ( 42 | // eslint-disable-next-line unicorn/no-array-reduce 43 | arguments_.reduce((p, c) => _defu(p, c, "", merger), {}) 44 | ); 45 | } 46 | const defuFn = createDefu((object, key, currentValue) => { 47 | if (typeof object[key] !== "undefined" && typeof currentValue === "function") { 48 | object[key] = currentValue(object[key]); 49 | return true; 50 | } 51 | }); 52 | const inlineConfig = {}; 53 | defuFn(inlineConfig); 54 | 55 | export { useHead as u }; 56 | //# sourceMappingURL=app.config-a3bb0fb9.mjs.map 57 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/error-500.5a01185b.js: -------------------------------------------------------------------------------- 1 | import{a as i,o as a,b as r,f as e,t as s,p as n,i as l}from"./entry.139a88d9.js";import{u as d}from"./app.config.e684eb09.js";const c=t=>(n("data-v-32388612"),t=t(),l(),t),p={class:"font-sans antialiased bg-white dark:bg-black text-black dark:text-white grid min-h-screen place-content-center overflow-hidden"},h=c(()=>e("div",{class:"fixed -bottom-1/2 left-0 right-0 h-1/2 spotlight"},null,-1)),f={class:"max-w-520px text-center"},m=["textContent"],g=["textContent"],x={__name:"error-500",props:{appName:{type:String,default:"Nuxt"},version:{type:String,default:""},statusCode:{type:Number,default:500},statusMessage:{type:String,default:"Server error"},description:{type:String,default:"This page is temporarily unavailable."}},setup(t){const o=t;return d({title:`${o.statusCode} - ${o.statusMessage} | ${o.appName}`,script:[],style:[{children:'*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}'}]}),(u,b)=>(a(),r("div",p,[h,e("div",f,[e("h1",{class:"text-8xl sm:text-10xl font-medium mb-8",textContent:s(t.statusCode)},null,8,m),e("p",{class:"text-xl px-8 sm:px-0 sm:text-4xl font-light mb-16 leading-tight",textContent:s(t.description)},null,8,g)])]))}},y=i(x,[["__scopeId","data-v-32388612"]]);export{y as default}; 2 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/error-500.aa16ed4d.css: -------------------------------------------------------------------------------- 1 | .spotlight[data-v-32388612]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);filter:blur(20vh)}.bg-white[data-v-32388612]{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.grid[data-v-32388612]{display:grid}.place-content-center[data-v-32388612]{place-content:center}.font-sans[data-v-32388612]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-medium[data-v-32388612]{font-weight:500}.font-light[data-v-32388612]{font-weight:300}.h-1\/2[data-v-32388612]{height:50%}.text-8xl[data-v-32388612]{font-size:6rem;line-height:1}.text-xl[data-v-32388612]{font-size:1.25rem;line-height:1.75rem}.leading-tight[data-v-32388612]{line-height:1.25}.mb-8[data-v-32388612]{margin-bottom:2rem}.mb-16[data-v-32388612]{margin-bottom:4rem}.max-w-520px[data-v-32388612]{max-width:520px}.min-h-screen[data-v-32388612]{min-height:100vh}.overflow-hidden[data-v-32388612]{overflow:hidden}.px-8[data-v-32388612]{padding-left:2rem;padding-right:2rem}.fixed[data-v-32388612]{position:fixed}.left-0[data-v-32388612]{left:0}.right-0[data-v-32388612]{right:0}.-bottom-1\/2[data-v-32388612]{bottom:-50%}.text-center[data-v-32388612]{text-align:center}.text-black[data-v-32388612]{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.antialiased[data-v-32388612]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (min-width:640px){.sm\:text-4xl[data-v-32388612]{font-size:2.25rem;line-height:2.5rem}.sm\:text-10xl[data-v-32388612]{font-size:10rem;line-height:1}.sm\:px-0[data-v-32388612]{padding-left:0;padding-right:0}}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-32388612]{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark\:text-white[data-v-32388612]{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}} 2 | -------------------------------------------------------------------------------- /server/database/schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | // 专栏 11 | model Column { 12 | id Int @id @default(autoincrement()) 13 | title String 14 | cover String 15 | desc String? 16 | content String? @db.Text 17 | url String? 18 | price Decimal? 19 | oPrice Decimal? 20 | } 21 | 22 | model Course { 23 | id Int @id @default(autoincrement()) 24 | title String 25 | cover String 26 | price Decimal 27 | oPrice Decimal 28 | desc String? 29 | detail String? @db.Text 30 | users UsersOnCourses[] 31 | orders Order[] 32 | Catalogue Catalogue[] 33 | url String? 34 | } 35 | 36 | // 章节 37 | model Catalogue { 38 | id Int @id @default(autoincrement()) 39 | title String 40 | source String 41 | course Course @relation(fields: [courseId], references: [id]) 42 | courseId Int 43 | } 44 | 45 | model User { 46 | id Int @id @default(autoincrement()) 47 | username String @unique 48 | password String 49 | nickname String? 50 | avatar String? 51 | sex String? 52 | courses UsersOnCourses[] 53 | orders Order[] 54 | } 55 | 56 | model UsersOnCourses { 57 | user User @relation(fields: [userId], references: [id]) 58 | userId Int 59 | course Course @relation(fields: [courseId], references: [id]) 60 | courseId Int 61 | 62 | @@id([userId, courseId]) 63 | } 64 | 65 | model Order { 66 | id Int @id @default(autoincrement()) 67 | course Course @relation(fields: [courseId], references: [id]) 68 | courseId Int 69 | user User @relation(fields: [userId], references: [id]) 70 | userId Int 71 | createdAt DateTime 72 | status OrderStatus 73 | } 74 | 75 | enum OrderStatus { 76 | WAIT_CONFIRM 77 | WAIT_PAY 78 | COMPLETED 79 | } 80 | -------------------------------------------------------------------------------- /components/MyHeader.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 76 | -------------------------------------------------------------------------------- /pages/login.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 83 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/app.config-a3bb0fb9.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.config-a3bb0fb9.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/app.config-a3bb0fb9.js"],"sourcesContent":null,"names":[],"mappings":";;AACA,SAAS,OAAA,CAAQ,OAAO,OAAS,EAAA;AAC/B,EAAA,OAAO,UAAW,EAAA,CAAE,QAAS,CAAA,KAAA,EAAO,OAAO,CAAA,CAAA;AAC7C,CAAA;AACA,SAAS,SAAS,KAAO,EAAA;AACvB,EAAO,OAAA,KAAA,KAAU,IAAQ,IAAA,OAAO,KAAU,KAAA,QAAA,CAAA;AAC5C,CAAA;AACA,SAAS,KAAM,CAAA,UAAA,EAAY,QAAU,EAAA,SAAA,GAAY,KAAK,MAAQ,EAAA;AAC5D,EAAI,IAAA,CAAC,QAAS,CAAA,QAAQ,CAAG,EAAA;AACvB,IAAA,OAAO,KAAM,CAAA,UAAA,EAAY,EAAC,EAAG,WAAW,MAAM,CAAA,CAAA;AAAA,GAChD;AACA,EAAA,MAAM,MAAS,GAAA,MAAA,CAAO,MAAO,CAAA,IAAI,QAAQ,CAAA,CAAA;AACzC,EAAA,KAAA,MAAW,OAAO,UAAY,EAAA;AAC5B,IAAI,IAAA,GAAA,KAAQ,WAAe,IAAA,GAAA,KAAQ,aAAe,EAAA;AAChD,MAAA,SAAA;AAAA,KACF;AACA,IAAM,MAAA,KAAA,GAAQ,WAAW,GAAG,CAAA,CAAA;AAC5B,IAAI,IAAA,KAAA,KAAU,IAAQ,IAAA,KAAA,KAAU,KAAQ,CAAA,EAAA;AACtC,MAAA,SAAA;AAAA,KACF;AACA,IAAA,IAAI,UAAU,MAAO,CAAA,MAAA,EAAQ,GAAK,EAAA,KAAA,EAAO,SAAS,CAAG,EAAA;AACnD,MAAA,SAAA;AAAA,KACF;AACA,IAAI,IAAA,KAAA,CAAM,QAAQ,KAAK,CAAA,IAAK,MAAM,OAAQ,CAAA,MAAA,CAAO,GAAG,CAAC,CAAG,EAAA;AACtD,MAAO,MAAA,CAAA,GAAG,IAAI,CAAC,GAAG,OAAO,GAAG,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AAAA,KACzC,MAAA,IAAW,SAAS,KAAK,CAAA,IAAK,SAAS,MAAO,CAAA,GAAG,CAAC,CAAG,EAAA;AACnD,MAAA,MAAA,CAAO,GAAG,CAAI,GAAA,KAAA;AAAA,QACZ,KAAA;AAAA,QACA,OAAO,GAAG,CAAA;AAAA,QAAA,CACT,SAAY,GAAA,CAAA,EAAG,SAAe,CAAA,CAAA,CAAA,GAAA,EAAA,IAAM,IAAI,QAAS,EAAA;AAAA,QAClD,MAAA;AAAA,OACF,CAAA;AAAA,KACK,MAAA;AACL,MAAA,MAAA,CAAO,GAAG,CAAI,GAAA,KAAA,CAAA;AAAA,KAChB;AAAA,GACF;AACA,EAAO,OAAA,MAAA,CAAA;AACT,CAAA;AACA,SAAS,WAAW,MAAQ,EAAA;AAC1B,EAAA,OAAO,CAAI,GAAA,UAAA;AAAA;AAAA,IAET,UAAW,CAAA,MAAA,CAAO,CAAC,CAAA,EAAG,CAAM,KAAA,KAAA,CAAM,CAAG,EAAA,CAAA,EAAG,EAAI,EAAA,MAAM,CAAG,EAAA,EAAE,CAAA;AAAA,GAAA,CAAA;AAE3D,CAAA;AACA,MAAM,MAAS,GAAA,UAAA,CAAW,CAAC,MAAA,EAAQ,KAAK,YAAiB,KAAA;AACvD,EAAA,IAAI,OAAO,MAAO,CAAA,GAAG,MAAM,WAAe,IAAA,OAAO,iBAAiB,UAAY,EAAA;AAC5E,IAAA,MAAA,CAAO,GAAG,CAAA,GAAI,YAAa,CAAA,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AACtC,IAAO,OAAA,IAAA,CAAA;AAAA,GACT;AACF,CAAC,CAAA,CAAA;AACD,MAAM,eAAe,EAAC,CAAA;AACtB,MAAA,CAAO,YAAY,CAAA;;;;"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-500-styles.6b5b5ff2.mjs: -------------------------------------------------------------------------------- 1 | const error500_vue_vue_type_style_index_0_scoped_32388612_lang = ".spotlight[data-v-32388612]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);filter:blur(20vh)}.bg-white[data-v-32388612]{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.grid[data-v-32388612]{display:grid}.place-content-center[data-v-32388612]{place-content:center}.font-sans[data-v-32388612]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-medium[data-v-32388612]{font-weight:500}.font-light[data-v-32388612]{font-weight:300}.h-1\\/2[data-v-32388612]{height:50%}.text-8xl[data-v-32388612]{font-size:6rem;line-height:1}.text-xl[data-v-32388612]{font-size:1.25rem;line-height:1.75rem}.leading-tight[data-v-32388612]{line-height:1.25}.mb-8[data-v-32388612]{margin-bottom:2rem}.mb-16[data-v-32388612]{margin-bottom:4rem}.max-w-520px[data-v-32388612]{max-width:520px}.min-h-screen[data-v-32388612]{min-height:100vh}.overflow-hidden[data-v-32388612]{overflow:hidden}.px-8[data-v-32388612]{padding-left:2rem;padding-right:2rem}.fixed[data-v-32388612]{position:fixed}.left-0[data-v-32388612]{left:0}.right-0[data-v-32388612]{right:0}.-bottom-1\\/2[data-v-32388612]{bottom:-50%}.text-center[data-v-32388612]{text-align:center}.text-black[data-v-32388612]{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.antialiased[data-v-32388612]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (min-width:640px){.sm\\:text-4xl[data-v-32388612]{font-size:2.25rem;line-height:2.5rem}.sm\\:text-10xl[data-v-32388612]{font-size:10rem;line-height:1}.sm\\:px-0[data-v-32388612]{padding-left:0;padding-right:0}}@media (prefers-color-scheme:dark){.dark\\:bg-black[data-v-32388612]{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark\\:text-white[data-v-32388612]{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}}"; 2 | 3 | const error500Styles_6b5b5ff2 = [error500_vue_vue_type_style_index_0_scoped_32388612_lang]; 4 | 5 | export { error500Styles_6b5b5ff2 as default }; 6 | //# sourceMappingURL=error-500-styles.6b5b5ff2.mjs.map 7 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-component-9ecfbe97.mjs: -------------------------------------------------------------------------------- 1 | import { useSSRContext, unref, mergeProps, defineAsyncComponent } from 'vue'; 2 | import { ssrRenderComponent } from 'vue/server-renderer'; 3 | 4 | const _sfc_main = { 5 | __name: "nuxt-error-page", 6 | __ssrInlineRender: true, 7 | props: { 8 | error: Object 9 | }, 10 | setup(__props) { 11 | var _a; 12 | const { error } = __props; 13 | (error.stack || "").split("\n").splice(1).map((line) => { 14 | const text = line.replace("webpack:/", "").replace(".vue", ".js").trim(); 15 | return { 16 | text, 17 | internal: line.includes("node_modules") && !line.includes(".cache") || line.includes("internal") || line.includes("new Promise") 18 | }; 19 | }).map((i) => `${i.text}`).join("\n"); 20 | const statusCode = Number(error.statusCode || 500); 21 | const is404 = statusCode === 404; 22 | const statusMessage = (_a = error.statusMessage) != null ? _a : is404 ? "Page Not Found" : "Internal Server Error"; 23 | const description = error.message || error.toString(); 24 | const stack = void 0; 25 | const _Error404 = /* @__PURE__ */ defineAsyncComponent(() => import('./error-404-59cb290a.mjs').then((r) => r.default || r)); 26 | const _Error = /* @__PURE__ */ defineAsyncComponent(() => import('./error-500-d1baf535.mjs').then((r) => r.default || r)); 27 | const ErrorTemplate = is404 ? _Error404 : _Error; 28 | return (_ctx, _push, _parent, _attrs) => { 29 | _push(ssrRenderComponent(unref(ErrorTemplate), mergeProps({ statusCode: unref(statusCode), statusMessage: unref(statusMessage), description: unref(description), stack: unref(stack) }, _attrs), null, _parent)); 30 | }; 31 | } 32 | }; 33 | const _sfc_setup = _sfc_main.setup; 34 | _sfc_main.setup = (props, ctx) => { 35 | const ssrContext = useSSRContext(); 36 | (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("node_modules/nuxt/dist/app/components/nuxt-error-page.vue"); 37 | return _sfc_setup ? _sfc_setup(props, ctx) : void 0; 38 | }; 39 | const _sfc_main$1 = _sfc_main; 40 | 41 | export { _sfc_main$1 as default }; 42 | //# sourceMappingURL=error-component-9ecfbe97.mjs.map 43 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/error-404.7326de30.js: -------------------------------------------------------------------------------- 1 | import{_ as a}from"./nuxt-link.dd8747fa.js";import{a as n,o as r,b as d,f as e,t as s,e as l,w as c,h as p,p as m,i as f}from"./entry.139a88d9.js";import{u as h}from"./app.config.e684eb09.js";const x=t=>(m("data-v-30d2164e"),t=t(),f(),t),u={class:"font-sans antialiased bg-white dark:bg-black text-black dark:text-white grid min-h-screen place-content-center overflow-hidden"},g=x(()=>e("div",{class:"fixed left-0 right-0 spotlight z-10"},null,-1)),_={class:"max-w-520px text-center z-20"},b=["textContent"],w=["textContent"],y={class:"w-full flex items-center justify-center"},S={__name:"error-404",props:{appName:{type:String,default:"Nuxt"},version:{type:String,default:""},statusCode:{type:Number,default:404},statusMessage:{type:String,default:"Not Found"},description:{type:String,default:"Sorry, the page you are looking for could not be found."},backHome:{type:String,default:"Go back home"}},setup(t){const o=t;return h({title:`${o.statusCode} - ${o.statusMessage} | ${o.appName}`,script:[],style:[{children:'*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}a{color:inherit;text-decoration:inherit}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}'}]}),(k,v)=>{const i=a;return r(),d("div",u,[g,e("div",_,[e("h1",{class:"text-8xl sm:text-10xl font-medium mb-8",textContent:s(t.statusCode)},null,8,b),e("p",{class:"text-xl px-8 sm:px-0 sm:text-4xl font-light mb-16 leading-tight",textContent:s(t.description)},null,8,w),e("div",y,[l(i,{to:"/",class:"gradient-border text-md sm:text-xl py-2 px-4 sm:py-3 sm:px-6 cursor-pointer"},{default:c(()=>[p(s(t.backHome),1)]),_:1})])])])}}},I=n(S,[["__scopeId","data-v-30d2164e"]]);export{I as default}; 2 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-404-59cb290a.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-404-59cb290a.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-404-59cb290a.js"],"sourcesContent":null,"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-component-9ecfbe97.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"error-component-9ecfbe97.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/error-component-9ecfbe97.js"],"sourcesContent":null,"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/client.manifest.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"client.manifest.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/client.manifest.mjs"],"sourcesContent":null,"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /pages/register.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 98 | -------------------------------------------------------------------------------- /server/database/migrations/20230404135755_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `Column` ( 3 | `id` INTEGER NOT NULL AUTO_INCREMENT, 4 | `title` VARCHAR(191) NOT NULL, 5 | `cover` VARCHAR(191) NOT NULL, 6 | `desc` VARCHAR(191) NULL, 7 | `content` TEXT NULL, 8 | 9 | PRIMARY KEY (`id`) 10 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 11 | 12 | -- CreateTable 13 | CREATE TABLE `Course` ( 14 | `id` INTEGER NOT NULL AUTO_INCREMENT, 15 | `title` VARCHAR(191) NOT NULL, 16 | `cover` VARCHAR(191) NOT NULL, 17 | `price` DECIMAL(65, 30) NOT NULL, 18 | `oPrice` DECIMAL(65, 30) NOT NULL, 19 | `desc` VARCHAR(191) NULL, 20 | `detail` TEXT NULL, 21 | 22 | PRIMARY KEY (`id`) 23 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 24 | 25 | -- CreateTable 26 | CREATE TABLE `Catalogue` ( 27 | `id` INTEGER NOT NULL AUTO_INCREMENT, 28 | `title` VARCHAR(191) NOT NULL, 29 | `source` VARCHAR(191) NOT NULL, 30 | `courseId` INTEGER NOT NULL, 31 | 32 | PRIMARY KEY (`id`) 33 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 34 | 35 | -- CreateTable 36 | CREATE TABLE `User` ( 37 | `id` INTEGER NOT NULL AUTO_INCREMENT, 38 | `username` VARCHAR(191) NOT NULL, 39 | `password` VARCHAR(191) NOT NULL, 40 | `nickname` VARCHAR(191) NULL, 41 | `avatar` VARCHAR(191) NULL, 42 | `sex` VARCHAR(191) NULL, 43 | 44 | UNIQUE INDEX `User_username_key`(`username`), 45 | PRIMARY KEY (`id`) 46 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 47 | 48 | -- CreateTable 49 | CREATE TABLE `UsersOnCourses` ( 50 | `userId` INTEGER NOT NULL, 51 | `courseId` INTEGER NOT NULL, 52 | 53 | PRIMARY KEY (`userId`, `courseId`) 54 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 55 | 56 | -- CreateTable 57 | CREATE TABLE `Order` ( 58 | `id` INTEGER NOT NULL AUTO_INCREMENT, 59 | `courseId` INTEGER NOT NULL, 60 | `userId` INTEGER NOT NULL, 61 | `createdAt` DATETIME(3) NOT NULL, 62 | `status` ENUM('WAIT_CONFIRM', 'WAIT_PAY', 'COMPLETED') NOT NULL, 63 | 64 | PRIMARY KEY (`id`) 65 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 66 | 67 | -- AddForeignKey 68 | ALTER TABLE `Catalogue` ADD CONSTRAINT `Catalogue_courseId_fkey` FOREIGN KEY (`courseId`) REFERENCES `Course`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 69 | 70 | -- AddForeignKey 71 | ALTER TABLE `UsersOnCourses` ADD CONSTRAINT `UsersOnCourses_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 72 | 73 | -- AddForeignKey 74 | ALTER TABLE `UsersOnCourses` ADD CONSTRAINT `UsersOnCourses_courseId_fkey` FOREIGN KEY (`courseId`) REFERENCES `Course`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 75 | 76 | -- AddForeignKey 77 | ALTER TABLE `Order` ADD CONSTRAINT `Order_courseId_fkey` FOREIGN KEY (`courseId`) REFERENCES `Course`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 78 | 79 | -- AddForeignKey 80 | ALTER TABLE `Order` ADD CONSTRAINT `Order_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 81 | -------------------------------------------------------------------------------- /pages/[type]/detail/[id].vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 101 | 102 | 107 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-500-d1baf535.mjs: -------------------------------------------------------------------------------- 1 | import { _ as _export_sfc } from '../server.mjs'; 2 | import { mergeProps, useSSRContext } from 'vue'; 3 | import { u as useHead } from './app.config-a3bb0fb9.mjs'; 4 | import { ssrRenderAttrs, ssrInterpolate } from 'vue/server-renderer'; 5 | import 'ofetch'; 6 | import 'hookable'; 7 | import 'unctx'; 8 | import '@unhead/vue'; 9 | import '@unhead/dom'; 10 | import '@unhead/ssr'; 11 | import 'vue-router'; 12 | import 'h3'; 13 | import 'ufo'; 14 | import 'defu'; 15 | import '../../nitro/vercel.mjs'; 16 | import 'node-fetch-native/polyfill'; 17 | import 'destr'; 18 | import 'unenv/runtime/fetch/index'; 19 | import 'scule'; 20 | import 'ohash'; 21 | import 'unstorage'; 22 | import 'radix3'; 23 | 24 | const _sfc_main = { 25 | __name: "error-500", 26 | __ssrInlineRender: true, 27 | props: { 28 | appName: { 29 | type: String, 30 | default: "Nuxt" 31 | }, 32 | version: { 33 | type: String, 34 | default: "" 35 | }, 36 | statusCode: { 37 | type: Number, 38 | default: 500 39 | }, 40 | statusMessage: { 41 | type: String, 42 | default: "Server error" 43 | }, 44 | description: { 45 | type: String, 46 | default: "This page is temporarily unavailable." 47 | } 48 | }, 49 | setup(__props) { 50 | const props = __props; 51 | useHead({ 52 | title: `${props.statusCode} - ${props.statusMessage} | ${props.appName}`, 53 | script: [], 54 | style: [ 55 | { 56 | children: `*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}` 57 | } 58 | ] 59 | }); 60 | return (_ctx, _push, _parent, _attrs) => { 61 | _push(`

${ssrInterpolate(__props.statusCode)}

${ssrInterpolate(__props.description)}

`); 62 | }; 63 | } 64 | }; 65 | const _sfc_setup = _sfc_main.setup; 66 | _sfc_main.setup = (props, ctx) => { 67 | const ssrContext = useSSRContext(); 68 | (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("node_modules/@nuxt/ui-templates/dist/templates/error-500.vue"); 69 | return _sfc_setup ? _sfc_setup(props, ctx) : void 0; 70 | }; 71 | const error500 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-32388612"]]); 72 | 73 | export { error500 as default }; 74 | //# sourceMappingURL=error-500-d1baf535.mjs.map 75 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/error-404.23f2309d.css: -------------------------------------------------------------------------------- 1 | .spotlight[data-v-30d2164e]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);bottom:-30vh;filter:blur(20vh);height:40vh}.gradient-border[data-v-30d2164e]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:.5rem;position:relative}@media (prefers-color-scheme:light){.gradient-border[data-v-30d2164e]{background-color:#ffffff4d}.gradient-border[data-v-30d2164e]:before{background:linear-gradient(90deg,#e2e2e2,#e2e2e2 25%,#00dc82 50%,#36e4da 75%,#0047e1)}}@media (prefers-color-scheme:dark){.gradient-border[data-v-30d2164e]{background-color:#1414144d}.gradient-border[data-v-30d2164e]:before{background:linear-gradient(90deg,#303030,#303030 25%,#00dc82 50%,#36e4da 75%,#0047e1)}}.gradient-border[data-v-30d2164e]:before{background-size:400% auto;border-radius:.5rem;bottom:0;content:"";left:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.5;padding:2px;position:absolute;right:0;top:0;transition:background-position .3s ease-in-out,opacity .2s ease-in-out;width:100%}.gradient-border[data-v-30d2164e]:hover:before{background-position:-50% 0;opacity:1}.bg-white[data-v-30d2164e]{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.cursor-pointer[data-v-30d2164e]{cursor:pointer}.flex[data-v-30d2164e]{display:flex}.grid[data-v-30d2164e]{display:grid}.place-content-center[data-v-30d2164e]{place-content:center}.items-center[data-v-30d2164e]{align-items:center}.justify-center[data-v-30d2164e]{justify-content:center}.font-sans[data-v-30d2164e]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-medium[data-v-30d2164e]{font-weight:500}.font-light[data-v-30d2164e]{font-weight:300}.text-8xl[data-v-30d2164e]{font-size:6rem;line-height:1}.text-xl[data-v-30d2164e]{font-size:1.25rem;line-height:1.75rem}.leading-tight[data-v-30d2164e]{line-height:1.25}.mb-8[data-v-30d2164e]{margin-bottom:2rem}.mb-16[data-v-30d2164e]{margin-bottom:4rem}.max-w-520px[data-v-30d2164e]{max-width:520px}.min-h-screen[data-v-30d2164e]{min-height:100vh}.overflow-hidden[data-v-30d2164e]{overflow:hidden}.px-8[data-v-30d2164e]{padding-left:2rem;padding-right:2rem}.py-2[data-v-30d2164e]{padding-bottom:.5rem;padding-top:.5rem}.px-4[data-v-30d2164e]{padding-left:1rem;padding-right:1rem}.fixed[data-v-30d2164e]{position:fixed}.left-0[data-v-30d2164e]{left:0}.right-0[data-v-30d2164e]{right:0}.text-center[data-v-30d2164e]{text-align:center}.text-black[data-v-30d2164e]{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.antialiased[data-v-30d2164e]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-full[data-v-30d2164e]{width:100%}.z-10[data-v-30d2164e]{z-index:10}.z-20[data-v-30d2164e]{z-index:20}@media (min-width:640px){.sm\:text-4xl[data-v-30d2164e]{font-size:2.25rem;line-height:2.5rem}.sm\:text-xl[data-v-30d2164e]{font-size:1.25rem;line-height:1.75rem}.sm\:text-10xl[data-v-30d2164e]{font-size:10rem;line-height:1}.sm\:px-0[data-v-30d2164e]{padding-left:0;padding-right:0}.sm\:py-3[data-v-30d2164e]{padding-bottom:.75rem;padding-top:.75rem}.sm\:px-6[data-v-30d2164e]{padding-left:1.5rem;padding-right:1.5rem}}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-30d2164e]{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark\:text-white[data-v-30d2164e]{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}} 2 | -------------------------------------------------------------------------------- /.vercel/output/static/_nuxt/nuxt-link.dd8747fa.js: -------------------------------------------------------------------------------- 1 | import{j as C,k as p,l as q,m as y,q as k,r as b,s as P,v as A,x,y as R,z as T}from"./entry.139a88d9.js";async function _(t,a=C()){if(a._routePreloaded||(a._routePreloaded=new Set),a._routePreloaded.has(t))return;const e=a._preloadPromises=a._preloadPromises||[];if(e.length>4)return Promise.all(e).then(()=>_(t,a));a._routePreloaded.add(t);const r=a.resolve(t).matched.map(o=>{var n;return(n=o.components)==null?void 0:n.default}).filter(o=>typeof o=="function");for(const o of r){const n=Promise.resolve(o()).catch(()=>{}).finally(()=>e.splice(e.indexOf(n)));e.push(n)}await Promise.all(e)}const g=globalThis.requestIdleCallback||(t=>{const a=Date.now(),e={didTimeout:!1,timeRemaining:()=>Math.max(0,50-(Date.now()-a))};return setTimeout(()=>{t(e)},1)}),N=globalThis.cancelIdleCallback||(t=>{clearTimeout(t)}),S=t=>{const a=p();a.isHydrating?a.hooks.hookOnce("app:suspense:resolve",()=>{g(t)}):g(t)},w=(...t)=>t.find(a=>a!==void 0),B="noopener noreferrer";function E(t){const a=t.componentName||"NuxtLink";return q({name:a,props:{to:{type:[String,Object],default:void 0,required:!1},href:{type:[String,Object],default:void 0,required:!1},target:{type:String,default:void 0,required:!1},rel:{type:String,default:void 0,required:!1},noRel:{type:Boolean,default:void 0,required:!1},prefetch:{type:Boolean,default:void 0,required:!1},noPrefetch:{type:Boolean,default:void 0,required:!1},activeClass:{type:String,default:void 0,required:!1},exactActiveClass:{type:String,default:void 0,required:!1},prefetchedClass:{type:String,default:void 0,required:!1},replace:{type:Boolean,default:void 0,required:!1},ariaCurrentValue:{type:String,default:void 0,required:!1},external:{type:Boolean,default:void 0,required:!1},custom:{type:Boolean,default:void 0,required:!1}},setup(e,{slots:r}){const o=C(),n=y(()=>e.to||e.href||""),c=y(()=>e.external||e.target&&e.target!=="_self"?!0:typeof n.value=="object"?!1:n.value===""||k(n.value,!0)),v=b(!1),s=b(null);if(e.prefetch!==!1&&e.noPrefetch!==!0&&typeof n.value=="string"&&e.target!=="_blank"&&!L()){const f=p();let u,l=null;P(()=>{const m=I();S(()=>{u=g(()=>{var d;(d=s==null?void 0:s.value)!=null&&d.tagName&&(l=m.observe(s.value,async()=>{l==null||l(),l=null,await Promise.all([f.hooks.callHook("link:prefetch",n.value).catch(()=>{}),!c.value&&_(n.value,o).catch(()=>{})]),v.value=!0}))})})}),A(()=>{u&&N(u),l==null||l(),l=null})}return()=>{var m,d;if(!c.value)return x(R("RouterLink"),{ref:h=>{s.value=h==null?void 0:h.$el},to:n.value,...v.value&&!e.custom?{class:e.prefetchedClass||t.prefetchedClass}:{},activeClass:e.activeClass||t.activeClass,exactActiveClass:e.exactActiveClass||t.exactActiveClass,replace:e.replace,ariaCurrentValue:e.ariaCurrentValue,custom:e.custom},r.default);const i=typeof n.value=="object"?((m=o.resolve(n.value))==null?void 0:m.href)??null:n.value||null,f=e.target||null,u=e.noRel?null:w(e.rel,t.externalRelAttribute,i?B:"")||null,l=()=>T(i,{replace:e.replace});return e.custom?r.default?r.default({href:i,navigate:l,route:o.resolve(i),rel:u,target:f,isExternal:c.value,isActive:!1,isExactActive:!1}):null:x("a",{ref:s,href:i,rel:u,target:f},(d=r.default)==null?void 0:d.call(r))}}})}const j=E({componentName:"NuxtLink"});function I(){const t=p();if(t._observer)return t._observer;let a=null;const e=new Map,r=(n,c)=>(a||(a=new IntersectionObserver(v=>{for(const s of v){const i=e.get(s.target);(s.isIntersecting||s.intersectionRatio>0)&&i&&i()}})),e.set(n,c),a.observe(n),()=>{e.delete(n),a.unobserve(n),e.size===0&&(a.disconnect(),a=null)});return t._observer={observe:r}}function L(){const t=navigator.connection;return!!(t&&(t.saveData||/2g/.test(t.effectiveType)))}export{j as _}; 2 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/client.manifest.mjs: -------------------------------------------------------------------------------- 1 | const client_manifest = { 2 | "_app.config.e684eb09.js": { 3 | "resourceType": "script", 4 | "module": true, 5 | "file": "app.config.e684eb09.js", 6 | "imports": [ 7 | "node_modules/nuxt/dist/app/entry.mjs" 8 | ] 9 | }, 10 | "_nuxt-link.dd8747fa.js": { 11 | "resourceType": "script", 12 | "module": true, 13 | "file": "nuxt-link.dd8747fa.js", 14 | "imports": [ 15 | "node_modules/nuxt/dist/app/entry.mjs" 16 | ] 17 | }, 18 | "node_modules/@nuxt/ui-templates/dist/templates/error-404.css": { 19 | "resourceType": "style", 20 | "file": "error-404.23f2309d.css", 21 | "src": "node_modules/@nuxt/ui-templates/dist/templates/error-404.css" 22 | }, 23 | "node_modules/@nuxt/ui-templates/dist/templates/error-404.vue": { 24 | "resourceType": "script", 25 | "module": true, 26 | "css": [], 27 | "file": "error-404.7326de30.js", 28 | "imports": [ 29 | "_nuxt-link.dd8747fa.js", 30 | "node_modules/nuxt/dist/app/entry.mjs", 31 | "_app.config.e684eb09.js" 32 | ], 33 | "isDynamicEntry": true, 34 | "src": "node_modules/@nuxt/ui-templates/dist/templates/error-404.vue" 35 | }, 36 | "error-404.23f2309d.css": { 37 | "file": "error-404.23f2309d.css", 38 | "resourceType": "style" 39 | }, 40 | "node_modules/@nuxt/ui-templates/dist/templates/error-500.css": { 41 | "resourceType": "style", 42 | "file": "error-500.aa16ed4d.css", 43 | "src": "node_modules/@nuxt/ui-templates/dist/templates/error-500.css" 44 | }, 45 | "node_modules/@nuxt/ui-templates/dist/templates/error-500.vue": { 46 | "resourceType": "script", 47 | "module": true, 48 | "css": [], 49 | "file": "error-500.5a01185b.js", 50 | "imports": [ 51 | "node_modules/nuxt/dist/app/entry.mjs", 52 | "_app.config.e684eb09.js" 53 | ], 54 | "isDynamicEntry": true, 55 | "src": "node_modules/@nuxt/ui-templates/dist/templates/error-500.vue" 56 | }, 57 | "error-500.aa16ed4d.css": { 58 | "file": "error-500.aa16ed4d.css", 59 | "resourceType": "style" 60 | }, 61 | "node_modules/nuxt/dist/app/entry.mjs": { 62 | "resourceType": "script", 63 | "module": true, 64 | "dynamicImports": [ 65 | "virtual:nuxt:/Users/57code/nuxt-app/.nuxt/error-component.mjs" 66 | ], 67 | "file": "entry.139a88d9.js", 68 | "isEntry": true, 69 | "src": "node_modules/nuxt/dist/app/entry.mjs" 70 | }, 71 | "pages/detail.vue": { 72 | "resourceType": "script", 73 | "module": true, 74 | "file": "detail.37523c12.js", 75 | "imports": [ 76 | "node_modules/nuxt/dist/app/entry.mjs" 77 | ], 78 | "isDynamicEntry": true, 79 | "src": "pages/detail.vue" 80 | }, 81 | "pages/index.vue": { 82 | "resourceType": "script", 83 | "module": true, 84 | "file": "index.d321cf39.js", 85 | "imports": [ 86 | "_nuxt-link.dd8747fa.js", 87 | "node_modules/nuxt/dist/app/entry.mjs" 88 | ], 89 | "isDynamicEntry": true, 90 | "src": "pages/index.vue" 91 | }, 92 | "virtual:nuxt:/Users/57code/nuxt-app/.nuxt/error-component.mjs": { 93 | "resourceType": "script", 94 | "module": true, 95 | "dynamicImports": [ 96 | "node_modules/@nuxt/ui-templates/dist/templates/error-404.vue", 97 | "node_modules/@nuxt/ui-templates/dist/templates/error-500.vue" 98 | ], 99 | "file": "error-component.3b96486c.js", 100 | "imports": [ 101 | "node_modules/nuxt/dist/app/entry.mjs" 102 | ], 103 | "isDynamicEntry": true, 104 | "src": "virtual:nuxt:/Users/57code/nuxt-app/.nuxt/error-component.mjs" 105 | } 106 | }; 107 | 108 | export { client_manifest as default }; 109 | //# sourceMappingURL=client.manifest.mjs.map 110 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-404-styles.a5c3f351.mjs: -------------------------------------------------------------------------------- 1 | const error404_vue_vue_type_style_index_0_scoped_30d2164e_lang = '.spotlight[data-v-30d2164e]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);bottom:-30vh;filter:blur(20vh);height:40vh}.gradient-border[data-v-30d2164e]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:.5rem;position:relative}@media (prefers-color-scheme:light){.gradient-border[data-v-30d2164e]{background-color:hsla(0,0%,100%,.3)}.gradient-border[data-v-30d2164e]:before{background:linear-gradient(90deg,#e2e2e2,#e2e2e2 25%,#00dc82 50%,#36e4da 75%,#0047e1)}}@media (prefers-color-scheme:dark){.gradient-border[data-v-30d2164e]{background-color:hsla(0,0%,8%,.3)}.gradient-border[data-v-30d2164e]:before{background:linear-gradient(90deg,#303030,#303030 25%,#00dc82 50%,#36e4da 75%,#0047e1)}}.gradient-border[data-v-30d2164e]:before{background-size:400% auto;border-radius:.5rem;bottom:0;content:"";left:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.5;padding:2px;position:absolute;right:0;top:0;transition:background-position .3s ease-in-out,opacity .2s ease-in-out;width:100%}.gradient-border[data-v-30d2164e]:hover:before{background-position:-50% 0;opacity:1}.bg-white[data-v-30d2164e]{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.cursor-pointer[data-v-30d2164e]{cursor:pointer}.flex[data-v-30d2164e]{display:flex}.grid[data-v-30d2164e]{display:grid}.place-content-center[data-v-30d2164e]{place-content:center}.items-center[data-v-30d2164e]{align-items:center}.justify-center[data-v-30d2164e]{justify-content:center}.font-sans[data-v-30d2164e]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-medium[data-v-30d2164e]{font-weight:500}.font-light[data-v-30d2164e]{font-weight:300}.text-8xl[data-v-30d2164e]{font-size:6rem;line-height:1}.text-xl[data-v-30d2164e]{font-size:1.25rem;line-height:1.75rem}.leading-tight[data-v-30d2164e]{line-height:1.25}.mb-8[data-v-30d2164e]{margin-bottom:2rem}.mb-16[data-v-30d2164e]{margin-bottom:4rem}.max-w-520px[data-v-30d2164e]{max-width:520px}.min-h-screen[data-v-30d2164e]{min-height:100vh}.overflow-hidden[data-v-30d2164e]{overflow:hidden}.px-8[data-v-30d2164e]{padding-left:2rem;padding-right:2rem}.py-2[data-v-30d2164e]{padding-bottom:.5rem;padding-top:.5rem}.px-4[data-v-30d2164e]{padding-left:1rem;padding-right:1rem}.fixed[data-v-30d2164e]{position:fixed}.left-0[data-v-30d2164e]{left:0}.right-0[data-v-30d2164e]{right:0}.text-center[data-v-30d2164e]{text-align:center}.text-black[data-v-30d2164e]{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.antialiased[data-v-30d2164e]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-full[data-v-30d2164e]{width:100%}.z-10[data-v-30d2164e]{z-index:10}.z-20[data-v-30d2164e]{z-index:20}@media (min-width:640px){.sm\\:text-4xl[data-v-30d2164e]{font-size:2.25rem;line-height:2.5rem}.sm\\:text-xl[data-v-30d2164e]{font-size:1.25rem;line-height:1.75rem}.sm\\:text-10xl[data-v-30d2164e]{font-size:10rem;line-height:1}.sm\\:px-0[data-v-30d2164e]{padding-left:0;padding-right:0}.sm\\:py-3[data-v-30d2164e]{padding-bottom:.75rem;padding-top:.75rem}.sm\\:px-6[data-v-30d2164e]{padding-left:1.5rem;padding-right:1.5rem}}@media (prefers-color-scheme:dark){.dark\\:bg-black[data-v-30d2164e]{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark\\:text-white[data-v-30d2164e]{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}}'; 2 | 3 | const error404Styles_a5c3f351 = [error404_vue_vue_type_style_index_0_scoped_30d2164e_lang]; 4 | 5 | export { error404Styles_a5c3f351 as default }; 6 | //# sourceMappingURL=error-404-styles.a5c3f351.mjs.map 7 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/error-500.mjs: -------------------------------------------------------------------------------- 1 | const _messages = {"appName":"Nuxt","version":"","statusCode":500,"statusMessage":"Server error","description":"This page is temporarily unavailable."}; 2 | const _render = function({ messages }) { 3 | var __t, __p = ''; 4 | __p += '' + 5 | ((__t = ( messages.statusCode )) == null ? '' : __t) + 6 | ' - ' + 7 | ((__t = ( messages.statusMessage )) == null ? '' : __t) + 8 | ' | ' + 9 | ((__t = ( messages.appName )) == null ? '' : __t) + 10 | '

' + 11 | ((__t = ( messages.statusCode )) == null ? '' : __t) + 12 | '

' + 13 | ((__t = ( messages.description )) == null ? '' : __t) + 14 | '

'; 15 | return __p 16 | }; 17 | const _template = (messages) => _render({ messages: { ..._messages, ...messages } }); 18 | const template = _template; 19 | 20 | export { template }; 21 | //# sourceMappingURL=error-500.mjs.map 22 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/error-404-59cb290a.mjs: -------------------------------------------------------------------------------- 1 | import { _ as __nuxt_component_0 } from './nuxt-link-58ca22a8.mjs'; 2 | import { _ as _export_sfc } from '../server.mjs'; 3 | import { mergeProps, withCtx, createTextVNode, toDisplayString, useSSRContext } from 'vue'; 4 | import { u as useHead } from './app.config-a3bb0fb9.mjs'; 5 | import { ssrRenderAttrs, ssrInterpolate, ssrRenderComponent } from 'vue/server-renderer'; 6 | import 'ufo'; 7 | import 'ofetch'; 8 | import 'hookable'; 9 | import 'unctx'; 10 | import '@unhead/vue'; 11 | import '@unhead/dom'; 12 | import '@unhead/ssr'; 13 | import 'vue-router'; 14 | import 'h3'; 15 | import 'defu'; 16 | import '../../nitro/vercel.mjs'; 17 | import 'node-fetch-native/polyfill'; 18 | import 'destr'; 19 | import 'unenv/runtime/fetch/index'; 20 | import 'scule'; 21 | import 'ohash'; 22 | import 'unstorage'; 23 | import 'radix3'; 24 | 25 | const _sfc_main = { 26 | __name: "error-404", 27 | __ssrInlineRender: true, 28 | props: { 29 | appName: { 30 | type: String, 31 | default: "Nuxt" 32 | }, 33 | version: { 34 | type: String, 35 | default: "" 36 | }, 37 | statusCode: { 38 | type: Number, 39 | default: 404 40 | }, 41 | statusMessage: { 42 | type: String, 43 | default: "Not Found" 44 | }, 45 | description: { 46 | type: String, 47 | default: "Sorry, the page you are looking for could not be found." 48 | }, 49 | backHome: { 50 | type: String, 51 | default: "Go back home" 52 | } 53 | }, 54 | setup(__props) { 55 | const props = __props; 56 | useHead({ 57 | title: `${props.statusCode} - ${props.statusMessage} | ${props.appName}`, 58 | script: [], 59 | style: [ 60 | { 61 | children: `*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}a{color:inherit;text-decoration:inherit}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}` 62 | } 63 | ] 64 | }); 65 | return (_ctx, _push, _parent, _attrs) => { 66 | const _component_NuxtLink = __nuxt_component_0; 67 | _push(`

${ssrInterpolate(__props.statusCode)}

${ssrInterpolate(__props.description)}

`); 68 | _push(ssrRenderComponent(_component_NuxtLink, { 69 | to: "/", 70 | class: "gradient-border text-md sm:text-xl py-2 px-4 sm:py-3 sm:px-6 cursor-pointer" 71 | }, { 72 | default: withCtx((_, _push2, _parent2, _scopeId) => { 73 | if (_push2) { 74 | _push2(`${ssrInterpolate(__props.backHome)}`); 75 | } else { 76 | return [ 77 | createTextVNode(toDisplayString(__props.backHome), 1) 78 | ]; 79 | } 80 | }), 81 | _: 1 82 | }, _parent)); 83 | _push(`
`); 84 | }; 85 | } 86 | }; 87 | const _sfc_setup = _sfc_main.setup; 88 | _sfc_main.setup = (props, ctx) => { 89 | const ssrContext = useSSRContext(); 90 | (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("node_modules/@nuxt/ui-templates/dist/templates/error-404.vue"); 91 | return _sfc_setup ? _sfc_setup(props, ctx) : void 0; 92 | }; 93 | const error404 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-30d2164e"]]); 94 | 95 | export { error404 as default }; 96 | //# sourceMappingURL=error-404-59cb290a.mjs.map 97 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/nuxt-link-58ca22a8.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"nuxt-link-58ca22a8.mjs","sources":["/Users/57code/nuxt-app/.nuxt/dist/server/_nuxt/nuxt-link-58ca22a8.js"],"sourcesContent":null,"names":["_a"],"mappings":"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"} -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/app/_nuxt/nuxt-link-58ca22a8.mjs: -------------------------------------------------------------------------------- 1 | import { defineComponent, computed, ref, h, resolveComponent } from 'vue'; 2 | import { hasProtocol } from 'ufo'; 3 | import { u as useRouter, n as navigateTo } from '../server.mjs'; 4 | 5 | const firstNonUndefined = (...args) => args.find((arg) => arg !== void 0); 6 | const DEFAULT_EXTERNAL_REL_ATTRIBUTE = "noopener noreferrer"; 7 | function defineNuxtLink(options) { 8 | const componentName = options.componentName || "NuxtLink"; 9 | return /* @__PURE__ */ defineComponent({ 10 | name: componentName, 11 | props: { 12 | // Routing 13 | to: { 14 | type: [String, Object], 15 | default: void 0, 16 | required: false 17 | }, 18 | href: { 19 | type: [String, Object], 20 | default: void 0, 21 | required: false 22 | }, 23 | // Attributes 24 | target: { 25 | type: String, 26 | default: void 0, 27 | required: false 28 | }, 29 | rel: { 30 | type: String, 31 | default: void 0, 32 | required: false 33 | }, 34 | noRel: { 35 | type: Boolean, 36 | default: void 0, 37 | required: false 38 | }, 39 | // Prefetching 40 | prefetch: { 41 | type: Boolean, 42 | default: void 0, 43 | required: false 44 | }, 45 | noPrefetch: { 46 | type: Boolean, 47 | default: void 0, 48 | required: false 49 | }, 50 | // Styling 51 | activeClass: { 52 | type: String, 53 | default: void 0, 54 | required: false 55 | }, 56 | exactActiveClass: { 57 | type: String, 58 | default: void 0, 59 | required: false 60 | }, 61 | prefetchedClass: { 62 | type: String, 63 | default: void 0, 64 | required: false 65 | }, 66 | // Vue Router's `` additional props 67 | replace: { 68 | type: Boolean, 69 | default: void 0, 70 | required: false 71 | }, 72 | ariaCurrentValue: { 73 | type: String, 74 | default: void 0, 75 | required: false 76 | }, 77 | // Edge cases handling 78 | external: { 79 | type: Boolean, 80 | default: void 0, 81 | required: false 82 | }, 83 | // Slot API 84 | custom: { 85 | type: Boolean, 86 | default: void 0, 87 | required: false 88 | } 89 | }, 90 | setup(props, { slots }) { 91 | const router = useRouter(); 92 | const to = computed(() => { 93 | return props.to || props.href || ""; 94 | }); 95 | const isExternal = computed(() => { 96 | if (props.external) { 97 | return true; 98 | } 99 | if (props.target && props.target !== "_self") { 100 | return true; 101 | } 102 | if (typeof to.value === "object") { 103 | return false; 104 | } 105 | return to.value === "" || hasProtocol(to.value, true); 106 | }); 107 | const prefetched = ref(false); 108 | const el = void 0; 109 | return () => { 110 | var _a2; 111 | var _a, _b; 112 | if (!isExternal.value) { 113 | return h( 114 | resolveComponent("RouterLink"), 115 | { 116 | ref: void 0, 117 | to: to.value, 118 | ...prefetched.value && !props.custom ? { class: props.prefetchedClass || options.prefetchedClass } : {}, 119 | activeClass: props.activeClass || options.activeClass, 120 | exactActiveClass: props.exactActiveClass || options.exactActiveClass, 121 | replace: props.replace, 122 | ariaCurrentValue: props.ariaCurrentValue, 123 | custom: props.custom 124 | }, 125 | slots.default 126 | ); 127 | } 128 | const href = typeof to.value === "object" ? (_a2 = (_a = router.resolve(to.value)) == null ? void 0 : _a.href) != null ? _a2 : null : to.value || null; 129 | const target = props.target || null; 130 | const rel = props.noRel ? null : firstNonUndefined(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : "") || null; 131 | const navigate = () => navigateTo(href, { replace: props.replace }); 132 | if (props.custom) { 133 | if (!slots.default) { 134 | return null; 135 | } 136 | return slots.default({ 137 | href, 138 | navigate, 139 | route: router.resolve(href), 140 | rel, 141 | target, 142 | isExternal: isExternal.value, 143 | isActive: false, 144 | isExactActive: false 145 | }); 146 | } 147 | return h("a", { ref: el, href, rel, target }, (_b = slots.default) == null ? void 0 : _b.call(slots)); 148 | }; 149 | } 150 | }); 151 | } 152 | const __nuxt_component_0 = /* @__PURE__ */ defineNuxtLink({ componentName: "NuxtLink" }); 153 | 154 | export { __nuxt_component_0 as _ }; 155 | //# sourceMappingURL=nuxt-link-58ca22a8.mjs.map 156 | -------------------------------------------------------------------------------- /server/database/seed.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | const prisma = new PrismaClient() 3 | async function main() { 4 | await prisma.column.create({ 5 | data: { 6 | title: 'Docker部署与持续集成', 7 | cover: 'Docker部署.png', 8 | url: 'https://duz.xet.tech/s/2zl8EZ', 9 | desc: '', 10 | content: '', 11 | oPrice: 299, 12 | price: 199, 13 | }, 14 | }) 15 | await prisma.column.create({ 16 | data: { 17 | title: 'Node服务器端', 18 | cover: 'Node与服务器端.jpg', 19 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_62b177bae4b09baaaaef38b9?product_id=p_62b177bae4b09baaaaef38b9', 20 | desc: '', 21 | content: '', 22 | oPrice: 699, 23 | price: 599, 24 | }, 25 | }) 26 | 27 | await prisma.column.create({ 28 | data: { 29 | title: '前端工程化', 30 | cover: '前端工程化.png', 31 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_62b17adae4b07bd2d7b0af40?product_id=p_62b17adae4b07bd2d7b0af40', 32 | desc: '', 33 | content: '', 34 | oPrice: 699, 35 | price: 599, 36 | }, 37 | }) 38 | 39 | await prisma.column.create({ 40 | data: { 41 | title: 'Webpack优化实战', 42 | cover: 'webpack优化.jpeg', 43 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_63252784e4b0a51fef1bb2dc?product_id=p_63252784e4b0a51fef1bb2dc', 44 | desc: '', 45 | content: '', 46 | oPrice: 399, 47 | price: 299, 48 | }, 49 | }) 50 | 51 | // await prisma.column.create({ 52 | // data: { 53 | // title: '上层框架最佳选择: Nuxt', 54 | // cover: 'column-nuxt.png', 55 | // desc: '上层框架最佳选择:Nuxt 是一个基于 Vue 的上层全栈通用框架,它提供了大量优秀特性提升开发效率和体验,因此是 Vue 栈上层框架的最佳选择之一。', 56 | // content: `开箱即用的开发环境 57 | // 开发者对一款现代框架的一个重要要求就是开箱即用。在这方面 Nuxt 提供了如下能力: 58 | 59 | // 整合 Vue3 作为视图引擎; 60 | // 整合 Webpack5 和 Vite 作为打包工具; 61 | // 提供最新 ES 语法,零配置 TS 支持; 62 | // 内置 vue-router,基于文件的路由; 63 | // 内置 SSR 友好的全局状态管理模块; 64 | // 内置数据访问模块 useFetch 等等。 65 | // 良好的开发体验 66 | // 良好的开发体验主要来源于效率工具和避免重复劳动,这方面我们看一下 Nuxt 提供的能力: 67 | 68 | // 基于文件的路由支持; 69 | // 组件、依赖库、工具集的自动导入; 70 | // 内置的数据获取模块和新的编程范式; 71 | // 零配置的 TS 支持; 72 | // 插件、模块、中间件等多种复用机制。 73 | // 服务端能力 74 | // Nuxt 内置了 Nitro 服务端引擎,能够同时提供 SSR 和 API 路由支持,这也就是说,除了能够提供服务端渲染能力,我们还能编写服务端接口,这使我们拥有了全栈开发能力。另外 API 兼容 node、connect、express,未来也可以把应用发布到 Node.js、Serverless 等服务器运行环境。 75 | 76 | // 不同场景解决方案 77 | // 为了满足开发者多种场景开发需求,Nuxt 提供了 5 种渲染模式: 78 | 79 | // 服务端渲染 SSR; 80 | // 客户端渲染 SPA; 81 | // 全静态内容生成 SSG; 82 | // 混合渲染模式 Hybrid; 83 | // 边缘渲染 Edge-render。 84 | // 在后面章节中,我们也将给大家详细介绍这几种模式的异同和选择。`, 85 | // }, 86 | // }) 87 | 88 | await prisma.course.create({ 89 | data: { 90 | title: 'React全栈实战', 91 | cover: 'React全栈进阶.png', 92 | oPrice: 899, 93 | price: 799, 94 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_6402a238e4b07b0558395e96', 95 | }, 96 | }) 97 | 98 | await prisma.course.create({ 99 | data: { 100 | title: 'Vue3组件库实战', 101 | cover: 'Vue3组件库实战.png', 102 | desc: '这门课我会全面讲解 Nuxt3 核心知识,然后在后端开发方面做一个知识扩展,最后带大家完成一个完整的实战项目。', 103 | oPrice: 699, 104 | price: 599, 105 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_62a44620e4b01c509abcbcda?product_id=p_62a44620e4b01c509abcbcda', 106 | }, 107 | }) 108 | await prisma.course.create({ 109 | data: { 110 | title: 'Vue源码全家桶', 111 | cover: 'Vue源码全家桶.jpeg', 112 | desc: '这门课我会全面讲解 Nuxt3 核心知识,然后在后端开发方面做一个知识扩展,最后带大家完成一个完整的实战项目。', 113 | oPrice: 699, 114 | price: 599, 115 | url: 'https://appwhrkrsz84443.pc.xiaoe-tech.com/p/t_pc/goods_pc_detail/goods_detail/p_62b4e11be4b0a51feef6bb4f?product_id=p_62b4e11be4b0a51feef6bb4f', 116 | }, 117 | }) 118 | 119 | await prisma.course.create({ 120 | data: { 121 | title: 'Nuxt全栈开发', 122 | cover: 'course-nuxt.png', 123 | desc: '这门课我会全面讲解 Nuxt3 核心知识,然后在后端开发方面做一个知识扩展,最后带大家完成一个完整的实战项目。', 124 | oPrice: 129, 125 | price: 99, 126 | detail: `这门课程共分五个模块: 127 | 模块一,将从渲染模式等基础概念出发,先扭转一些同学的固有思维,补充缺失知识; 128 | 模块二,结合个人博客案例,深入学习 Nuxt3 核心特性; 129 | 模块三,解决项目工程化问题,从扩展性、复用性等角度深入了解模块等框架进阶知识; 130 | 模块四,将为项目实战做准备,给大家讲解全栈知识,包括数据库设计、接口设计和开发,大家会接触并掌握 Apifox、Prisma 等前端比较时髦的新工具; 131 | 模块五,项目实战,我会带大家开发一个知识分享社区主题的全栈项目,包括了从接口开发,到前端开发,再到优化、部署和持续集成的全流程实战。 132 | 相信学习完本小册,会让你深入掌握 Nuxt3 的同时,还能全方位提升自己的知识深度和架构水平。`, 133 | Catalogue: { 134 | createMany: { 135 | data: [ 136 | { title: '01开篇:课程介绍和安排', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 137 | { title: '02上层框架最佳选择', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 138 | { title: '03五种渲染模式', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 139 | { title: '04快速创建首个nuxt项目', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 140 | { title: '05文件路由和布局', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 141 | { title: '06使用静态资源', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 142 | { title: '07自动导入特性', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 143 | { title: '08API路由', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 144 | { title: '09数据获取', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 145 | { title: '10状态管理', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 146 | { title: '11错误处理', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 147 | { title: '12常用配置和优化', source: 'https://juejin.cn/video/7202149403342143520/section/7202885295820242947' }, 148 | ], 149 | }, 150 | }, 151 | }, 152 | }) 153 | } 154 | main() 155 | .then(async () => { 156 | await prisma.$disconnect() 157 | }) 158 | .catch(async (e) => { 159 | console.error(e) 160 | await prisma.$disconnect() 161 | process.exit(1) 162 | }) 163 | -------------------------------------------------------------------------------- /.vercel/output/functions/__nitro.func/chunks/handlers/renderer.mjs: -------------------------------------------------------------------------------- 1 | import { createRenderer } from 'vue-bundle-renderer/runtime'; 2 | import { eventHandler, getQuery, createError } from 'h3'; 3 | import { renderToString } from 'vue/server-renderer'; 4 | import { u as useNitroApp, a as useRuntimeConfig, g as getRouteRules } from '../nitro/vercel.mjs'; 5 | import { joinURL } from 'ufo'; 6 | import 'node-fetch-native/polyfill'; 7 | import 'ofetch'; 8 | import 'destr'; 9 | import 'unenv/runtime/fetch/index'; 10 | import 'hookable'; 11 | import 'scule'; 12 | import 'ohash'; 13 | import 'unstorage'; 14 | import 'defu'; 15 | import 'radix3'; 16 | 17 | function defineRenderHandler(handler) { 18 | return eventHandler(async (event) => { 19 | if (event.node.req.url.endsWith("/favicon.ico")) { 20 | event.node.res.setHeader("Content-Type", "image/x-icon"); 21 | event.node.res.end( 22 | "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 23 | ); 24 | return; 25 | } 26 | const response = await handler(event); 27 | if (!response) { 28 | if (!event.node.res.writableEnded) { 29 | event.node.res.statusCode = event.node.res.statusCode === 200 ? 500 : event.node.res.statusCode; 30 | event.node.res.end( 31 | "No response returned from render handler: " + event.node.req.url 32 | ); 33 | } 34 | return; 35 | } 36 | const nitroApp = useNitroApp(); 37 | await nitroApp.hooks.callHook("render:response", response, { event }); 38 | if (!event.node.res.headersSent && response.headers) { 39 | for (const header in response.headers) { 40 | event.node.res.setHeader(header, response.headers[header]); 41 | } 42 | if (response.statusCode) { 43 | event.node.res.statusCode = response.statusCode; 44 | } 45 | if (response.statusMessage) { 46 | event.node.res.statusMessage = response.statusMessage; 47 | } 48 | } 49 | return typeof response.body === "string" ? response.body : JSON.stringify(response.body); 50 | }); 51 | } 52 | 53 | const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"; 54 | const unsafeChars = /[<>\b\f\n\r\t\0\u2028\u2029]/g; 55 | const reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/; 56 | const escaped = { 57 | "<": "\\u003C", 58 | ">": "\\u003E", 59 | "/": "\\u002F", 60 | "\\": "\\\\", 61 | "\b": "\\b", 62 | "\f": "\\f", 63 | "\n": "\\n", 64 | "\r": "\\r", 65 | " ": "\\t", 66 | "\0": "\\0", 67 | "\u2028": "\\u2028", 68 | "\u2029": "\\u2029" 69 | }; 70 | const objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join("\0"); 71 | function devalue(value) { 72 | const counts = new Map(); 73 | let logNum = 0; 74 | function log(message) { 75 | if (logNum < 100) { 76 | console.warn(message); 77 | logNum += 1; 78 | } 79 | } 80 | function walk(thing) { 81 | if (typeof thing === "function") { 82 | log(`Cannot stringify a function ${thing.name}`); 83 | return; 84 | } 85 | if (counts.has(thing)) { 86 | counts.set(thing, counts.get(thing) + 1); 87 | return; 88 | } 89 | counts.set(thing, 1); 90 | if (!isPrimitive(thing)) { 91 | const type = getType(thing); 92 | switch (type) { 93 | case "Number": 94 | case "String": 95 | case "Boolean": 96 | case "Date": 97 | case "RegExp": 98 | return; 99 | case "Array": 100 | thing.forEach(walk); 101 | break; 102 | case "Set": 103 | case "Map": 104 | Array.from(thing).forEach(walk); 105 | break; 106 | default: 107 | const proto = Object.getPrototypeOf(thing); 108 | if (proto !== Object.prototype && proto !== null && Object.getOwnPropertyNames(proto).sort().join("\0") !== objectProtoOwnPropertyNames) { 109 | if (typeof thing.toJSON !== "function") { 110 | log(`Cannot stringify arbitrary non-POJOs ${thing.constructor.name}`); 111 | } 112 | } else if (Object.getOwnPropertySymbols(thing).length > 0) { 113 | log(`Cannot stringify POJOs with symbolic keys ${Object.getOwnPropertySymbols(thing).map((symbol) => symbol.toString())}`); 114 | } else { 115 | Object.keys(thing).forEach((key) => walk(thing[key])); 116 | } 117 | } 118 | } 119 | } 120 | walk(value); 121 | const names = new Map(); 122 | Array.from(counts).filter((entry) => entry[1] > 1).sort((a, b) => b[1] - a[1]).forEach((entry, i) => { 123 | names.set(entry[0], getName(i)); 124 | }); 125 | function stringify(thing) { 126 | if (names.has(thing)) { 127 | return names.get(thing); 128 | } 129 | if (isPrimitive(thing)) { 130 | return stringifyPrimitive(thing); 131 | } 132 | const type = getType(thing); 133 | switch (type) { 134 | case "Number": 135 | case "String": 136 | case "Boolean": 137 | return `Object(${stringify(thing.valueOf())})`; 138 | case "RegExp": 139 | return thing.toString(); 140 | case "Date": 141 | return `new Date(${thing.getTime()})`; 142 | case "Array": 143 | const members = thing.map((v, i) => i in thing ? stringify(v) : ""); 144 | const tail = thing.length === 0 || thing.length - 1 in thing ? "" : ","; 145 | return `[${members.join(",")}${tail}]`; 146 | case "Set": 147 | case "Map": 148 | return `new ${type}([${Array.from(thing).map(stringify).join(",")}])`; 149 | default: 150 | if (thing.toJSON) { 151 | let json = thing.toJSON(); 152 | if (getType(json) === "String") { 153 | try { 154 | json = JSON.parse(json); 155 | } catch (e) { 156 | } 157 | } 158 | return stringify(json); 159 | } 160 | if (Object.getPrototypeOf(thing) === null) { 161 | if (Object.keys(thing).length === 0) { 162 | return "Object.create(null)"; 163 | } 164 | return `Object.create(null,{${Object.keys(thing).map((key) => `${safeKey(key)}:{writable:true,enumerable:true,value:${stringify(thing[key])}}`).join(",")}})`; 165 | } 166 | return `{${Object.keys(thing).map((key) => `${safeKey(key)}:${stringify(thing[key])}`).join(",")}}`; 167 | } 168 | } 169 | const str = stringify(value); 170 | if (names.size) { 171 | const params = []; 172 | const statements = []; 173 | const values = []; 174 | names.forEach((name, thing) => { 175 | params.push(name); 176 | if (isPrimitive(thing)) { 177 | values.push(stringifyPrimitive(thing)); 178 | return; 179 | } 180 | const type = getType(thing); 181 | switch (type) { 182 | case "Number": 183 | case "String": 184 | case "Boolean": 185 | values.push(`Object(${stringify(thing.valueOf())})`); 186 | break; 187 | case "RegExp": 188 | values.push(thing.toString()); 189 | break; 190 | case "Date": 191 | values.push(`new Date(${thing.getTime()})`); 192 | break; 193 | case "Array": 194 | values.push(`Array(${thing.length})`); 195 | thing.forEach((v, i) => { 196 | statements.push(`${name}[${i}]=${stringify(v)}`); 197 | }); 198 | break; 199 | case "Set": 200 | values.push("new Set"); 201 | statements.push(`${name}.${Array.from(thing).map((v) => `add(${stringify(v)})`).join(".")}`); 202 | break; 203 | case "Map": 204 | values.push("new Map"); 205 | statements.push(`${name}.${Array.from(thing).map(([k, v]) => `set(${stringify(k)}, ${stringify(v)})`).join(".")}`); 206 | break; 207 | default: 208 | values.push(Object.getPrototypeOf(thing) === null ? "Object.create(null)" : "{}"); 209 | Object.keys(thing).forEach((key) => { 210 | statements.push(`${name}${safeProp(key)}=${stringify(thing[key])}`); 211 | }); 212 | } 213 | }); 214 | statements.push(`return ${str}`); 215 | return `(function(${params.join(",")}){${statements.join(";")}}(${values.join(",")}))`; 216 | } else { 217 | return str; 218 | } 219 | } 220 | function getName(num) { 221 | let name = ""; 222 | do { 223 | name = chars[num % chars.length] + name; 224 | num = ~~(num / chars.length) - 1; 225 | } while (num >= 0); 226 | return reserved.test(name) ? `${name}0` : name; 227 | } 228 | function isPrimitive(thing) { 229 | return Object(thing) !== thing; 230 | } 231 | function stringifyPrimitive(thing) { 232 | if (typeof thing === "string") { 233 | return stringifyString(thing); 234 | } 235 | if (thing === void 0) { 236 | return "void 0"; 237 | } 238 | if (thing === 0 && 1 / thing < 0) { 239 | return "-0"; 240 | } 241 | const str = String(thing); 242 | if (typeof thing === "number") { 243 | return str.replace(/^(-)?0\./, "$1."); 244 | } 245 | return str; 246 | } 247 | function getType(thing) { 248 | return Object.prototype.toString.call(thing).slice(8, -1); 249 | } 250 | function escapeUnsafeChar(c) { 251 | return escaped[c] || c; 252 | } 253 | function escapeUnsafeChars(str) { 254 | return str.replace(unsafeChars, escapeUnsafeChar); 255 | } 256 | function safeKey(key) { 257 | return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escapeUnsafeChars(JSON.stringify(key)); 258 | } 259 | function safeProp(key) { 260 | return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? `.${key}` : `[${escapeUnsafeChars(JSON.stringify(key))}]`; 261 | } 262 | function stringifyString(str) { 263 | let result = '"'; 264 | for (let i = 0; i < str.length; i += 1) { 265 | const char = str.charAt(i); 266 | const code = char.charCodeAt(0); 267 | if (char === '"') { 268 | result += '\\"'; 269 | } else if (char in escaped) { 270 | result += escaped[char]; 271 | } else if (code >= 55296 && code <= 57343) { 272 | const next = str.charCodeAt(i + 1); 273 | if (code <= 56319 && (next >= 56320 && next <= 57343)) { 274 | result += char + str[++i]; 275 | } else { 276 | result += `\\u${code.toString(16).toUpperCase()}`; 277 | } 278 | } else { 279 | result += char; 280 | } 281 | } 282 | result += '"'; 283 | return result; 284 | } 285 | 286 | const appRootId = "__nuxt"; 287 | 288 | const appRootTag = "div"; 289 | 290 | function buildAssetsURL(...path) { 291 | return joinURL(publicAssetsURL(), useRuntimeConfig().app.buildAssetsDir, ...path); 292 | } 293 | function publicAssetsURL(...path) { 294 | const publicBase = useRuntimeConfig().app.cdnURL || useRuntimeConfig().app.baseURL; 295 | return path.length ? joinURL(publicBase, ...path) : publicBase; 296 | } 297 | 298 | globalThis.__buildAssetsURL = buildAssetsURL; 299 | globalThis.__publicAssetsURL = publicAssetsURL; 300 | const getClientManifest = () => import('../app/client.manifest.mjs').then((r) => r.default || r).then((r) => typeof r === "function" ? r() : r); 301 | const getStaticRenderedHead = () => import('../rollup/_virtual_head-static.mjs').then((r) => r.default || r); 302 | const getServerEntry = () => import('../app/server.mjs').then((r) => r.default || r); 303 | const getSSRStyles = lazyCachedFunction(() => import('../app/styles.mjs').then((r) => r.default || r)); 304 | const getSSRRenderer = lazyCachedFunction(async () => { 305 | const manifest = await getClientManifest(); 306 | if (!manifest) { 307 | throw new Error("client.manifest is not available"); 308 | } 309 | const createSSRApp = await getServerEntry(); 310 | if (!createSSRApp) { 311 | throw new Error("Server bundle is not available"); 312 | } 313 | const options = { 314 | manifest, 315 | renderToString: renderToString$1, 316 | buildAssetsURL 317 | }; 318 | const renderer = createRenderer(createSSRApp, options); 319 | async function renderToString$1(input, context) { 320 | const html = await renderToString(input, context); 321 | return `<${appRootTag} id="${appRootId}">${html}`; 322 | } 323 | return renderer; 324 | }); 325 | const getSPARenderer = lazyCachedFunction(async () => { 326 | const manifest = await getClientManifest(); 327 | const options = { 328 | manifest, 329 | renderToString: () => `<${appRootTag} id="${appRootId}">`, 330 | buildAssetsURL 331 | }; 332 | const renderer = createRenderer(() => () => { 333 | }, options); 334 | const result = await renderer.renderToString({}); 335 | const renderToString = (ssrContext) => { 336 | const config = useRuntimeConfig(); 337 | ssrContext.payload = { 338 | serverRendered: false, 339 | config: { 340 | public: config.public, 341 | app: config.app 342 | }, 343 | data: {}, 344 | state: {} 345 | }; 346 | ssrContext.renderMeta = ssrContext.renderMeta ?? getStaticRenderedHead; 347 | return Promise.resolve(result); 348 | }; 349 | return { 350 | rendererContext: renderer.rendererContext, 351 | renderToString 352 | }; 353 | }); 354 | const PAYLOAD_URL_RE = /\/_payload(\.[a-zA-Z0-9]+)?.js(\?.*)?$/; 355 | const renderer = defineRenderHandler(async (event) => { 356 | const nitroApp = useNitroApp(); 357 | const ssrError = event.node.req.url?.startsWith("/__nuxt_error") ? getQuery(event) : null; 358 | if (ssrError && ssrError.statusCode) { 359 | ssrError.statusCode = parseInt(ssrError.statusCode); 360 | } 361 | if (ssrError && event.node.req.socket.readyState !== "readOnly") { 362 | throw createError("Cannot directly render error page!"); 363 | } 364 | const islandContext = void 0; 365 | let url = ssrError?.url || islandContext?.url || event.node.req.url; 366 | const isRenderingPayload = PAYLOAD_URL_RE.test(url); 367 | if (isRenderingPayload) { 368 | url = url.substring(0, url.lastIndexOf("/")) || "/"; 369 | event.node.req.url = url; 370 | } 371 | const routeOptions = getRouteRules(event); 372 | const ssrContext = { 373 | url, 374 | event, 375 | runtimeConfig: useRuntimeConfig(), 376 | noSSR: !!event.node.req.headers["x-nuxt-no-ssr"] || routeOptions.ssr === false || (false), 377 | error: !!ssrError, 378 | nuxt: void 0, 379 | /* NuxtApp */ 380 | payload: ssrError ? { error: ssrError } : {}, 381 | islandContext 382 | }; 383 | const renderer = ssrContext.noSSR ? await getSPARenderer() : await getSSRRenderer(); 384 | const _rendered = await renderer.renderToString(ssrContext).catch((error) => { 385 | throw !ssrError && ssrContext.payload?.error || error; 386 | }); 387 | await ssrContext.nuxt?.hooks.callHook("app:rendered", { ssrContext }); 388 | if (ssrContext.payload?.error && !ssrError) { 389 | throw ssrContext.payload.error; 390 | } 391 | if (isRenderingPayload) { 392 | const response2 = renderPayloadResponse(ssrContext); 393 | return response2; 394 | } 395 | const renderedMeta = await ssrContext.renderMeta?.() ?? {}; 396 | const inlinedStyles = await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? []) ; 397 | const htmlContext = { 398 | island: Boolean(islandContext), 399 | htmlAttrs: normalizeChunks([renderedMeta.htmlAttrs]), 400 | head: normalizeChunks([ 401 | renderedMeta.headTags, 402 | null, 403 | _rendered.renderResourceHints(), 404 | _rendered.renderStyles(), 405 | inlinedStyles, 406 | ssrContext.styles 407 | ]), 408 | bodyAttrs: normalizeChunks([renderedMeta.bodyAttrs]), 409 | bodyPrepend: normalizeChunks([ 410 | renderedMeta.bodyScriptsPrepend, 411 | ssrContext.teleports?.body 412 | ]), 413 | body: [_rendered.html], 414 | bodyAppend: normalizeChunks([ 415 | `