python 做网站 套件,嘉兴网站设计,网页无法访问6,公司内部网站维护theme: smartblue
最近公司立项了一个新项目#xff0c;因为是to C 的#xff0c;所以对SEO是有较高需求的#xff0c;由于公司前端技术栈统一用的VUE#xff0c;顺理成章的就选择了nuxt这个全栈框架。项目立项之后我就被安排了负责前端项目框架的搭建#xff0c;从搭建过…
theme: smartblue
最近公司立项了一个新项目因为是to C 的所以对SEO是有较高需求的由于公司前端技术栈统一用的VUE顺理成章的就选择了nuxt这个全栈框架。项目立项之后我就被安排了负责前端项目框架的搭建从搭建过程的体验来看技术栈切换到nuxt还是有门槛的所以这里我就把经过我打磨好的nuxt完整项目框架分享出来大家即拿即用童叟无欺。顺嘴提一句欢迎大家关注我的微信公众号fever code获取最新技术分享。
项目结构
- api
- assets
-- images
-- lang
---- en_us.json
---- zh_cn.json
-- scss
---- constants.scss
---- index.scss
- components
- composables
-- store
-- pinia
-- locale.js
-- auth.js
-- toast.js
- configs
- constants
-- auth.js
- layouts
-- default.vue
-- login.vue
- middleware
-- default.global.js
- pages
- plugins
-- pinia.js
- public
- server
-- api
---- list.js
-- middleware
---- request.js
- utils
-- http.js
- .env.development
- .env.local
- .env.production
- .eslintrc.cjs
- .gitignore
- .prettierrc.json
- app.vue
- error.vue
- i18n.config.js
- nuxt.config.ts
- package.jsonNUXT项目配置
下面贴一下nuxt项目配置的代码。因为UI设计师选用的样式风格是arco-design组件库的风格所以项目中也集成的acro-design。项目采用的适配方案是px-to-vw状态管理工具是nuxt自带的useState和pinia简单的状态管理采用useState就够了复杂的状态就决定使用pinia分模块化管理。项目的受众群体囊括了海内外的业内人士所以在项目中也做了国际化的处理环境变量使用的是VITE_开头客户端可以使用import.env.meta访问也可以使用useRuntimeConfig获得。
// https://nuxt.com/docs/api/configuration/nuxt-configexport default defineNuxtConfig({devtools: { enabled: false },app: {head: {titleTemplate: %s - 京东商城,title: 京东商城,charset: utf-8,htmlAttrs: {lang: zh-CN},meta: [{ name: keywords, content: 网上购物,网上商城,家电,手机,电脑,服装,居家,母婴,美妆,个护,食品,生鲜,京东 },{name: description,content:京东JD.COM-专业的综合网上购物商城为您提供正品低价的购物选择、优质便捷的服务体验。商品来自全球数十万品牌商家囊括家电、手机、电脑、服装、居家、母婴、美妆、个护、食品、生鲜等丰富品类满足各种购物需求。}]}},// 注入全局样式css: [~/assets/scss/index.scss],modules: [// arco-design UI组件库arco-design-nuxt-module,// 国际化插件nuxtjs/i18n,[// pinia状态管理库pinia/nuxt,{// 项目中自动导入pinia的defineStore方法autoImports: [defineStore]}]],// arco-design UI组件库配置arco: {importPrefix: A,hookPrefix: Arco,locales: [getLocale],localePrefix: Arco},// 国际化插件配置i18n: {strategy: no_prefix, // 添加路由前缀的方式locales: [en, zh], //配置语种defaultLocale: zh, // 默认语种vueI18n: i18n.config.js // 通过vueI18n配置},// nuxt组件库配置components: [{path: ~/components,extensions: [.vue],pathPrefix: false}],imports: {dirs: [// 扫描composables目录中的所有包括子文件夹模块composables/**]},// 发服务器配置devServer: {port: 3001},build: {// 在开发环境和生产环境对es包使用babel进行语法转换transpile: [element-plus/es]},vite: {css: {preprocessorOptions: {scss: {// 全局引入scss常量供全局使用additionalData: import assets/scss/constant.scss;}}}},postcss: {plugins: {postcss-px-to-viewport: {viewportWidth: 1920 /** 设计稿的视口宽度 */,unitToConvert: px /** 需要转换的单位默认为px */,unitPrecision: 5 /** 单位转换后保留的精度 */,propList: [*] /** 能转化为vw的属性列表 */,viewportUnit: vw /** 希望使用的视口单位 */,fontViewportUnit: vw /** 字体使用的视口单位 */,selectorBlackList: [] /** 需要忽略的CSS选择器 */,minPixelValue: 1 /** 设置最小的转换数值 */,mediaQuery: false /** 媒体查询里的单位是否需要转换单位 */,replace: true /** 是否直接更换属性值而不添加备用属性 */,exclude: undefined /** 忽略某些文件夹下的文件或特定文件 */,include: undefined /** 设置将只有匹配到的文件才会被转换 */,landscape: false /** 是否添加根据 landscapeWidth 生成的媒体查询条件 media */,landscapeUnit: vw /** 横屏时使用的单位 */,landscapeWidth: undefined /** 横屏时使用的视口宽度 */}}},runtimeConfig: {mode: process.env.VITE_MODE,base_url: process.env.VITE_BASE_URL,app: {mode: process.env.VITE_MODE,base_url: process.env.VITE_BASE_URL,}}
})客户端请求HTTP模块封装
Nuxt作为一个全栈框架是有客户端和服务端的概念的。在使用nuxt开发的过程中很多人有一个误区认为前端所有的请求都要发送到nuxt服务端nuxt服务端请求后端接口后返回前端。其实不是这样的只有需要SSR服务端渲染的内容才需要这样做也就是需要渲染的数据的GET请求才需要这样做对于增加、删除、修改这一类请求完全可以直接在客户端向后端发请求。这里贴一下我封装HTTP模块请求库用的是nuxt自带的$fetch。
/*** description http模块*/
import { jumpToLogin } from /utils/utils// 接口基地址
const BASE_URL import.meta.env.VITE_BASE_URL// 环境
const MODE import.meta.env.VITE_MODE// 生产环境
const MODE_PRODUCTION production// GET请求方法
const METHOD_GET GET// 成功状态
const SUCCESS_STATUS_TEXT OK// 响应类型
const RESPONSE_TYPE [blob, stream]// 请求拦截器
const requestInterceptor (config) {if (config.options.meta?.needAuth) {const { getToken, getUid } useAuth()const token getToken()const uid getUid()const method config.options.method?.toUpperCase()if (method METHOD_GET) {const query config.options.query || {}config.options.query { ...query, token, uid }} else {const body config.options.body || {}config.options.body { ...body, token, uid }}}return config
} // 响应拦截器
const responseInterceptor (response) {const res response.responseif (res.status 200 res.statusText SUCCESS_STATUS_TEXT res._data.data RESPONSE_TYPE.includes(res?.type)) {return response}if (MODE ! MODE_PRODUCTION) {console.log(res.url, {code: res._data.code,data: res._data.data,res: res._data,params: response.options,resHeaders: res.headers})}if (res._data.code 0 || res._data.code 200) {return response} else if (res._data.code -50) {// token过期或失效const routeMeta useRouteMeta()const { removeToken, removeUid } useAuth()const userInfo useUserInfo()removeToken()removeUid()// 清空用户信息userInfo.value {}if (routeMeta.value.needAuth) {// 当前页面需要权限的话登录失效即跳转登录页jumpToLogin()}return Promise.reject(res._data)}return Promise.reject(res._data)
} // 错误拦截器
const errorInterceptor (err) {return Promise.reject(err.error)
}const httpInstance $fetch.create({baseURL: BASE_URL,onRequest: requestInterceptor,onResponse: responseInterceptor,onRequestError: errorInterceptor
}) export default httpInstanceapi接口管理模块
// /api/common.js/*** description 项目中公共请求api*/import http from /utils/common // 获取上传临时密钥
export function getCommonList() {return http(/api/common/getList, {method: get,meta: {// 标记该接口是否需要权限校验需要则会在请求拦截器那里做请求拦截相关逻辑处理needAuth: true}})
}NUXT服务端请求
nuxt服务端接口开发请求流程前端 -- nuxt服务端 -- java等服务端接口
// /server/api/list.jsimport { readRawBody, getQuery, getMethod } from h3export default defineEventHandler(async (event) {// const res await useFetchData(/user-center/user/getUserInfo, {// method,// baseURL: event.context.baseUrl,// headers: event.context.headers,// params: getQuery(event),// body// })const method getMethod(event).toUpperCase()let bodyif (method ! GET) body await readRawBody(event)const res await $fetch(/user-center/user/getUserInfo, {method,baseURL: event.context.baseUrl,headers: event.context.headers,params: getQuery(event),body})return res || { userInfo: {} }
})nuxt服务端中间件处理请求所有通往nuxt服务端的请求以及nuxt服务端响应前端过程都会在这里被拦截处理
import { getHeaders } from h3export default defineEventHandler((event) {const reqHeaders getHeaders(event)const ssrHeader new Headers()const { app } useRuntimeConfig()ssrHeader.set(cookie, reqHeaders.cookie)// 往nuxt请求注入请求基地址event.context.baseUrl app.base_url// 往nuxt请求注入请求头event.context.headers ssrHeader
})前使用useFetch调用nuxt接口即可
srcipt setup
const { data: result } await useFetch(/api/list)
/script路由守卫
nuxt中的路由守卫是在客户端middleware使用defineNuxtRouteMiddleware路由中间件功能实现的。nuxt中设计的客户端的middleware主要是用来拦截路由的即客户端的路由切换会通过客户端的middleware。如果客户端向nuxt服务端发请求则请求都会被服务端的middleware拦截处理。客户端的中间件文件名如果使用.global.js则nuxt会识别为这是一个全局生效的中间件。由于我们这里实现的是全局路由守卫功能所以文件名命名为default.global.js。
// /middleware/default.global.js/*** description 全局路由守卫*/
import { jumpToLogin } from /utils/utilsexport default defineNuxtRouteMiddleware((to, from) {const routeMeta useRouteMeta()const { getToken, getUid } useAuth()if (to.meta.needAuth (!getToken() || !getUid())) {// 需要授权的页面验证是否登录jumpToLogin()return abortNavigation()}// 记录当前要访问页面的元信息必须置于鉴权逻辑之后供http模块鉴权之用routeMeta.value to.meta || {}
})状态管理
项目中的状态管理我同时使用了nuxt集成的轻量级的useState和vue官方推荐的pinia
使用useState封装的hook直接放到/composables/store文件夹下
// composables/store/base.js/*** description 项目基础状态管理模块*/import { USER_INFO, ROUTE_META, LOCALE_TYPE } from /constants/state// 用户信息管理
export const useUserInfo () useState(USER_INFO, () {return {}
})// 路由元数据
export const useRouteMeta () useState(ROUTE_META, () {return {}
})// 国际化 - 本地语言类型
export const useLocale () useState(LOCALE_TYPE, () {return zh
})pinia模块直接放到/composables/pinia文件夹下管理维护。因为nuxt中pinia存在刷新状态丢失的情况所以在客户端的plugins中集成了pinia-plugin-persistedstate插件实现状态数据持久化能力。
// plugins/pinia.js
// https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html/*** description pinia数据持久化插件*/
import piniaPluginPersistedstate from pinia-plugin-persistedstateexport default defineNuxtPlugin((nuxtApp) {nuxtApp.$pinia.use(piniaPluginPersistedstate)
})国际化
项目中使用的国际化插件是vue-i18n配置如下
// i18n.config.jsimport en from assets/lang/en_us.json;
import zh from assets/lang/zh_cn.json;export default defineI18nConfig(() ({legacy: false, // 是否兼容之前fallbackLocale: en, // 区配不到的语言就用enmessages: {en,zh}
}))// /assets/lang/en_us.json
{home: Home}// /assets/lang/zh_cn.json
{home: 主页}为了使用方便我特地在文件夹下封装了一个hook
/*** description 提供国际化功能的hook*/export function useI18nHook() {const { locale, setLocale } useI18n()const localeLang useLocale()// 初始化本地语言类型localeLang.value localeconst setLocaleLang (type) {setLocale(type)localeLang.value type}return {locale: localeLang,setLocale: setLocaleLang}
}在.vue模版中使用直接如下
templatediv{{ $t(home) }}
/template不仅页面代码需要国际化处理UI组件库也需要国际化处理acro-designUI库使用a-config-provider标签在App.vue文件中全局注入本地语言
templatediva-config-provider :localelocaleNuxtLayoutNuxtPage //NuxtLayout/a-config-provider/div
/templatescript setup
import enUS from arco-design/web-vue/es/locale/lang/en-us
import zhCN from arco-design/web-vue/es/locale/lang/zh-cnconst locales {zh: zhCN,en: enUS
}const { locale: langLocale } useI18nHook()const locale computed(() {return locales[langLocale] || zhCN
})useHead({link: [{ rel: shortcut icon, href: /favicon.ico },{ rel: apple-touch-icon, href: /favicon.ico }]
})
/script其他配置
Layout
// /layout/default.vuetemplatediv classlayout-containerHeader /main classcontentslot //mainFooter //div
/templatestyle scoped langscss
.layout-container {min-height: 100vh;.content {min-height: calc(100vh - 447px);background: #f7f8fa;}
}
/style权限管理HOOK
// /composables/auth.js/*** description 提供权限管理功能*/
import { TOKEN_KEY, UID_KEY } from /constants/authconst MODE import.meta.env.VITE_MODEconst DomainMap {development: demo.com,production: demo.cn,
}export function useAuth() {const cookieToken useCookie(TOKEN_KEY, { domain: DomainMap[MODE] }const cookieUID useCookie(UID_KEY, { domain: DomainMap[MODE] })const getToken () {return cookieToken.value}const setToken (token) {cookieToken.value token}const removeToken () {cookieToken.value undefined}const getUid () {return cookieUID.value}const setUid (uid) {cookieUID.value uid}const removeUid () {cookieUID.value undefined}return {getToken,setToken,removeToken,getUid,setUid,removeUid,}
}全局Toast HOOK
toast提示使用的是vue-toast-notification插件
// /composables/toast.js/*** description 全局用toast提示方法*/
import { useToast as useToastNotification } from vue-toast-notificationexport function useToast() {return useToastNotification()
}Eslint配置
配置里继承的nuxt/eslint-config的规则较为严格不用也可以去掉
module.exports {root: true,extends: [nuxt/eslint-config],parserOptions: {ecmaVersion: latest},rules: {vue/multi-word-component-names: 0/**针对单个单词组件报错的规则关闭 */,no-undef: 0, /**关闭变量未定义检查 */no-debugger: 2 /**禁用 debugger */,no-dupe-args: 2 /**禁止 function 定义中出现重名参数 */,no-dupe-keys: 2 /**禁止对象字面量中出现重复的 key */,no-empty: 1 /**禁止出现空语句块 */,no-ex-assign: 1 /**禁止对 catch 子句的参数重新赋值 */,no-extra-boolean-cast: 1 /**禁止不必要的布尔转换 */,no-extra-parens: 1 /**禁止不必要的括号 */,no-extra-semi: 1 /**禁止不必要的分号 */,no-func-assign: 1 /**禁止对 function 声明重新赋值 */,no-irregular-whitespace: 1 /**禁止在字符串和注释之外不规则的空白 */,no-unexpected-multiline: 1 /**禁止出现令人困惑的多行表达式 */,no-unreachable: 1 /**禁止在return、throw、continue 和 break语句之后出现不可达代码 */,use-isnan: 1 /**要求使用 isNaN() 检查 NaN */,dot-location: 1 /**强制在点号之前和之后一致的换行 */,eqeqeq: 2 /**要求使用 和 ! */,no-alert: 1 /**禁用 alert、confirm 和 prompt */,no-case-declarations: 1 /**不允许在 case 子句中使用词法声明 */,no-else-return: 1 /**禁止 if 语句中有 return 之后有 else */,no-empty-function: 1 /**禁止出现空函数 */,no-eq-null: 1 /**禁止在没有类型检查操作符的情况下与 null 进行比较 */,no-eval: 1 /**禁用 eval() */,no-fallthrough: 1 /**禁止 case 语句落空 */,no-lone-blocks: 1 /**禁用不必要的嵌套块 */,no-redeclare: 1 /**禁止使用 var 多次声明同一变量 */,no-self-assign: 1 /**禁止自我赋值 */,no-self-compare: 1 /**禁止自身比较 */,no-unmodified-loop-condition: 1 /**禁用一成不变的循环条件 */,vars-on-top: 1 /**要求所有的 var 声明出现在它们所在的作用域顶部 */,eol-last: 1 /**强制文件末尾至少保留一行空行强制文件末尾至少保留一行空行 */,}
}Premitter配置
{$schema: https://json.schemastore.org/prettierrc,semi: false,tabWidth: 2,singleQuote: true,printWidth: 100,trailingComma: none,useTabs: true
}package.json
{name: app,private: true,type: module,scripts: {build: nuxt build --dotenv .env.production,build:dev: nuxt build --dotenv .env.development,serve: nuxt dev --dotenv .env.local,generate: nuxt generate,preview: nuxt preview --dotenv .env.production,postinstall: nuxt prepare,lint: eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore,format: prettier},dependencies: {pinia/nuxt: ^0.5.1,arco-design-nuxt-module: ^0.1.0,blueimp-md5: ^2.19.0,clipboard: ^2.0.11,cos-js-sdk-v5: ^1.8.1,dayjs: ^1.11.11,nuxt: ^3.12.2,pinia: ^2.1.7,pinia-plugin-persistedstate: ^3.2.1,vue: ^3.4.29,vue-i18n: ^9.13.1,vue-router: ^4.3.3,vue-toast-notification: ^3.1.2},devDependencies: {nuxt/eslint-config: ^0.3.13,nuxtjs/i18n: ^8.3.1,postcss-aspect-ratio-mini: ^1.1.0,postcss-px-to-viewport: ^1.1.1,prettier: ^3.3.2,sass: ^1.77.6,sass-loader: ^14.2.1}
}环境变量
环境变量定义了.local.env、.production.env、.development.env这里只单列一个
# 环境变量
VITE_MODEdevelopment# 接口基地址
VITE_BASE_URLhttps://demo.cn写在最后
扫码关注作者微信公众号fever code获取一手技术分享