直接拉取项目
npm i
运行
npm run dev:h5
自行cli搭建步骤
安装
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
npm i
npm i pinia -S --legacy-peer-deps
npm i pinia-plugin-persistedstate -S --legacy-peer-deps
npm i sass -D --legacy-peer-deps
npm i sass-loader -D --legacy-peer-deps
npm i vk-uview-ui --legacy-peer-deps
运行报错
Uncaught SyntaxError: The requested module '/node_modules/pinia/node_modules/vue-demi/lib/index.mjs?v=f43e2f61' does not provide an export named 'hasInjectionContext'
解决:降低pinia版本:npm install pinia@2.0.36 --legacy-peer-deps
运行报错
Uncaught TypeError: Cannot destructure property 'proxy' of 'getCurrentInstance(...)' as it is null.
解决:全局挂载的方法,在页面中使用,ts/js文件中还是用导入模块/函数的方式
一、安装完成后
配置uView Vue3.0
//main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";
// 引入 uView UI
+ import uView from 'vk-uview-ui';
export function createApp() {
const app = createSSRApp(App);
// 使用 uView
+ app.use(uView)
return {
app
};
}
//pages.json
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^u-(.*)": "vk-uview-ui/components/u-$1/u-$1.vue"
+ }
+ },
//App.vue
<style lang="scss">
+ @import "vk-uview-ui/index.scss";
</style>
//uni.scss
@import "vk-uview-ui/theme.scss";
配置pinia
//main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";
// 导入 pinia 实例
+ import pinia from './stores'
export function createApp() {
const app = createSSRApp(App);
// 使用 pinia
+ app.use(pinia)
return {
app,
};
}
//store/index.ts
import * as Pinia from 'pinia';
import persist from 'pinia-plugin-persistedstate'
// 创建 pinia 实例
const pinia = Pinia.createPinia();
// 使用持久化存储插件
pinia.use(persist)
// 默认导出,给 main.ts 使用
export default pinia
导入主题样式文件
# 相关文件
src/common
src/static/common/common.scss
//App.vue
<style lang="scss">
// 主题样式
@import '@/common/theme/css/theme.scss';
/* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
@import './common/uni.css';
</style>
存储主题信息,开启持久化
//store/theme.ts
import { defineStore } from 'pinia';
interface themeStoreType {
color: string;
index: number|string;
}
interface themeStoreState {
appTheme: string;
appThemeIndex: number|string;
theme: string | themeStoreType;
}
const themeStore = defineStore('themeStore', {
state: (): themeStoreState => ({
// appTheme: localStorage.getSto('appTheme') ? localStorage.getSto('appTheme') : 'index',
// appThemeIndex: localStorage.getSto('appThemeIndex') ? localStorage.getSto('appThemeIndex') : 1,
appTheme: 'index',
appThemeIndex: 1,
theme: '',
}),
persist: {
storage: {
getItem: uni.getStorageSync,
setItem: uni.setStorageSync
}
},
getters: {
},
actions: {
//你可以传入一个颜色参数(需要上面公共css中含有,如果不传入默认)
setTheme(data?: themeStoreType) {
//你可以传入一个颜色参数(需要上面公共css中含有,如果不传入默认)
this.$patch({
appTheme: data.color,
appThemeIndex: data.index,
theme: data
});
console.log('修改', this.theme);
}
}
});
export default themeStore;
定义修改主题颜色的方法并导出
// src/utils/theme.ts
import themeStore from '@/stores/theme';
//在ts/js文件中,需要引入实例store,才能使用模块
import store from '@/stores/index'
const theme = themeStore(store);
export const toggleAppTheme = (color, index) => {
var data = {
color: color,
index: index
};
theme.setTheme(data);
uni.hideLoading();
if (color == 'index') {
return '1';
} else if (color == 'black') {
return '2';
} else if (color == 'blue') {
return '3';
} else if (color == 'grey') {
uni.hideLoading();
return '4';
} else if (color == 'green') {
uni.hideLoading();
return '5';
} else if (color == 'yellow') {
return '6';
} else {
return '1';
}
};
全局挂载主题色和修改主题方法
//main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";
// 主题切换
+ import useThemeStore from '@/stores/theme';
+ const theme = useThemeStore();
+ import { toggleAppTheme } from '@/utils/theme';
export function createApp() {
const app = createSSRApp(App);
// 全局挂载
+ app.config.globalProperties.$appThemeName = theme; //主题颜色
+ app.config.globalProperties.$changeAppTheme = toggleAppTheme; //改变主题色事件
return {
app
};
}
渲染主题
//App.vue
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
//引入API
+ import { getCurrentInstance } from 'vue';
+ const { proxy } = getCurrentInstance() as any;
//定义全局挂载的主题色及方法
+ const themeStore = proxy.$appThemeName;
+ const toggleAppTheme = proxy.$changeAppTheme;
onLaunch(() => {
console.log("App Launch");
});
onShow(() => {
console.log("App Show");
//使用定义的全局挂载的主题色及方法
+ if (themeStore.appThemeIndex) {
+ console.log('app.vue', themeStore.appTheme, themeStore.appThemeIndex);
+ toggleAppTheme(themeStore.appTheme, themeStore.appThemeIndex);
+ } else {
+ toggleAppTheme('index', '1');
+ }
});
onHide(() => {
console.log("App Hide");
});
</script>
<style lang="scss">
// 主题样式
@import '@/common/theme/css/theme.scss';
/* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
@import './common/uni.css';
</style>
页面路径集中管理
//utils/routes.ts
export const index_route = '/pages/index/index';
export const mine_route = '/pages/mine/mine';
导入自定义组件,并全局挂载
# 相关文件
# 二次封装uView自定义顶部导航栏
src/components/ownNavbar/ownNavbar.vue
# 自定义底部导航栏
src/components/tabBar/tabBar.vue
//ownNavbar.vue
<template>
<view class="">
<u-navbar
:back-text="backText"
:title-width="titleWidth"
:title="title"
:background="background"
:title-color="color"
:back-icon-color="color"
:is-back="isBack"
:border-bottom="borderbottom"
>
<view slot="right">
<slot name="rights"></slot>
</view>
</u-navbar>
</view>
</template>
<script lang="ts" setup>
// import themeMxins from "@/utils/mixins/themeMxins.js"
import { ref, computed,getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance() as any;
const themeStore = proxy.$appThemeName;
const toggleAppTheme = proxy.$changeAppTheme;
const props = defineProps({
title: {
type: String,
default: ''
},
//标题宽度
titleWidth: {
type: String,
default: 300
},
//标题宽度
backText: {
type: String,
default: ''
},
/* 是否返回 */
isBack: {
type: Boolean,
default: true
},
/* 底部线条 */
borderbottom: {
type: Boolean,
default: true
},
color: {
type: String,
default: '#fff'
}
});
const background = computed(() => {
let type = toggleAppTheme(themeStore.appTheme, themeStore.appThemeIndex);
console.log('ttttype',type)
if (type == '1') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(32, 171, 254), rgb(60, 199, 254))'
};
} else if (type == '2') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(78, 78, 78), rgb(78, 78, 78))'
};
} else if (type == '3') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(47, 166, 200), rgb(47, 166, 200))'
};
} else if (type == '4') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(214, 214, 214), rgb(214, 214, 214))'
};
} else if (type == '5') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(62, 130, 83), rgb(62, 130, 83))'
};
} else if (type == '6') {
return {
backgroundImage: 'linear-gradient(45deg, rgb(239, 150, 24), rgb(239, 150, 24))'
};
} else {
return {
backgroundImage: 'linear-gradient(45deg, rgb(32, 171, 254), rgb(60, 199, 254))'
};
}
});
const color = computed(() => {
let type = toggleAppTheme(themeStore.appTheme, themeStore.appThemeIndex);
if (type == '1') {
return '#fff';
} else if (type == '2') {
return '#fff';
} else if (type == '3') {
return '#fff';
} else if (type == '4') {
return '#000';
} else if (type == '5') {
return '#fff';
} else if (type == '6') {
return '#fff';
} else {
return '#fff';
}
});
</script>
<style></style>
//tabBar.vue
<template>
<view class="tabbar" :data-theme="themeStore.appTheme" :style="{ 'padding-bottom': paddingBottomHeight + 'rpx' }">
<view class="tabbar-item" v-for="(item, index) in tabList" :key="index" @click="tabbarChange(item.pagePath)">
<view class="">
<u-icon v-if="current == index" class="tab-icon-select" size="45" :name="item.selectedIconPath"
custom-prefix="custom-icon"></u-icon>
<u-icon v-else class="tab-icon" size="45" :name="item.iconPath" custom-prefix="custom-icon"></u-icon>
</view>
<view class="">
<view class="tabbarActive" v-if="current == index">{{ item.text }}</view>
<view class="tab-text" v-else>{{ item.text }}</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance } from 'vue';
import { index_route, mine_route } from '@/utils/routes'
const { proxy } = getCurrentInstance() as any;
const themeStore = proxy.$appThemeName;
const props = defineProps({
current: {
type: Number,
default: 0
}
});
const tabList = ref([
{
iconPath: 'shouye',
selectedIconPath: 'shouye-fill',
text: '首页',
count: 0, // 红色角标显示的数字,如果需要移除角标,配置此参数为0即可
isDot: false, // 如果配置此值为true,那么角标将会以红点的形式显示
customIcon: true, // 如果使用自定义扩展的图标库字体,需配置此值为true
midButton: false, // 如果是凸起按钮项,需配置此值为true
pagePath: index_route //路径需要以"/"开头
},
{
iconPath: 'wode',
selectedIconPath: 'wode-fill',
text: '主题',
isDot: false,
customIcon: true,
midButton: false,
pagePath: mine_route
}
]);
const paddingBottomHeight = ref(0);
const active = ref(props.current ? props.current : 0);
// uni.hideTabBar();
uni.getSystemInfo({
success: function (res) {
let model = ['X', 'XR', 'XS', '11', '12', '13', '14', '15'];
model.forEach((item) => {
//适配iphoneX以上的底部,给tabbar一定高度的padding-bottom
if (res.model.indexOf(item) != -1 && res.model.indexOf('iPhone') != -1) {
// that.paddingBottomHeight = 40;
}
});
}
});
// const tabChange = (index, urls) => {
// if (index == active.value) {
// return;
// }
// active.value = index;
// console.log(index, active.value);
// uni.switchTab({
// url: urls
// });
// };
const tabbarChange = (urls: string) => {
uni.redirectTo({
url: urls
});
};
</script>
<style lang="scss" scoped>
.tabbar {
width: 100%;
// height: 100rpx;
border-top: 1px solid #c0c0c0;
display: flex;
position: fixed;
justify-content: space-around;
z-index: 99 !important;
background-color: #ffffff;
bottom: 0;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
// padding: 20rpx 0;
.tabbar-item {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10rpx 0;
.tab-text {
font-size: 26rpx;
color: #919191;
}
.tabbarActive {
font-size: 26rpx;
color: #67a9fd;
}
.tab-icon {
color: #919191;
}
.tab-icon-select {
color: #67a9fd;
}
}
}
</style>
全局挂载注册自定义组件
//main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";
//引入自定义导航
+ import ownNavbar from '@/components/ownNavbar/ownNavbar.vue'; //导航栏
+ import tabBar from '@/components/tabBar/tabBar.vue'; //底部导航栏
export function createApp() {
const app = createSSRApp(App);
// 全局注册自定义组件
+ app.component('own-navbar', ownNavbar);
+ app.component('tab-bar', tabBar);
return {
app
};
}
创建页面,关闭原生顶部导航
//index.vue
<template>
<view class="bodys flex-column boxSizing">
<own-navbar title="首页" :isBack="false"></own-navbar>
<view class="content boxSizing">
<button :data-theme="themeStore.appTheme" type="default" size="mini" style="margin: 30rpx">按钮</button>
<button :data-theme="themeStore.appTheme" type="default" size="mini" style="margin: 30rpx">按钮</button>
<button :data-theme="themeStore.appTheme" type="default" size="mini" style="margin: 30rpx">按钮</button>
</view>
<tab-bar :current="0"></tab-bar>
</view>
</template>
<script lang="ts" setup>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance() as any;
const themeStore = proxy.$appThemeName
</script>
<style lang="scss" scoped>
.bodys {
background-color: #e7e7e7;
min-height: 100vh;
}
</style>
//mine.vue
<template>
<view class="bodys boxSizing">
<own-navbar title="主题设置" :isBack="false"></own-navbar>
<view class="title" :data-theme="themeStore.appTheme">系统主题</view>
<view class="content">
<view class="theme_box flex-box boxSizing">
<view v-for="(info, index) in themes" :key="index" class="theme boxSizing flex-center" :class="info.theme" @click="toggleAppTheme(info.theme, info.index)">
<u-icon name="checkmark" color="#fff" size="48" v-if="themeStore.appThemeIndex == (index+1)"></u-icon>
</view>
</view>
</view>
<view class="bgColor" :data-theme="themeStore.appTheme" style="height: 300rpx; width: 80%; margin: 0 auto"></view>
<tab-bar :current="1"></tab-bar>
</view>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance() as any;
const themeStore = proxy.$appThemeName
const toggleAppTheme = proxy.$changeAppTheme
const themes = ref([
{
index: 1,
theme: 'index',
name: '默认色'
},
{
index: 2,
theme: 'black',
name: '极致黑'
},
{
index: 3,
theme: 'blue',
name: '天青蓝'
},
{
index: 4,
theme: 'grey',
name: '素雅灰'
},
{
index: 5,
theme: 'green',
name: '国网绿'
},
{
index: 6,
theme: 'yellow',
name: '深桔黄'
}
]);
</script>
<style lang="scss" scoped>
.bodys {
min-height: 100vh;
background: #f4f4f4;
.title {
padding: 20rpx;
}
.content {
.flex-box {
display: flex;
}
.theme_box {
background-color: #fff;
flex-wrap: wrap;
padding: 20rpx 15rpx;
.theme {
width: 30%;
height: 100rpx !important;
margin-bottom: 20rpx;
font-size: 30rpx;
line-height: 100rpx;
text-align: center;
color: #fff;
position: relative;
box-shadow: 3px 3px 5px #d0d0d3;
margin: 0 10rpx 15rpx 10rpx;
text {
z-index: 9;
}
.dian {
width: 20rpx;
height: 20rpx;
position: absolute;
background-color: #fff;
border-radius: 50%;
top: 3px;
right: 3px;
border: 1px solid #000000;
z-index: 99 !important;
}
}
.theme:last-child {
margin-right: 0;
}
}
.theme_box_check {
border: 2px solid #007aff;
}
.index {
background-color: #67a9fd;
}
.black {
background-color: #4e4e4e;
}
.blue {
background-color: #2fa6c8;
}
.grey {
background-color: #dddddd;
}
.green {
background-color: #3e8253;
}
.yellow {
background-color: #ef9618;
}
}
}
</style>
配置关闭原生顶部导航
//pages.json
"pages": [
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app",
//关闭原生顶部导航
+ "navigationStyle": "custom"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "mine",
+ "navigationStyle": "custom"
}
}
]