Appearance
uniapp统一样式类名使用指南
一、 动态样式风格配置 (style)
应用主题通过在页面根元素上添加 style
计算属性实现,然后给对应的标签添加样式类名,即可应用动态的主题样式,目前支持n-card
、n-btn-primary
、n-btn-outline
等。
注: 必须在页面根元素上添加 style
才生效
使用方式
vue
<template>
<view :style="appStyle">
<!-- 页面内容 -->
<view class="good-box n-card">
<!-- 卡片内容 -->
<view class="n-tag">标签1</view>
</view>
<view class="n-btn-primary">立即购买</view>
<view class="n-btn-outline">加入购物车</view>
</view>
</template>
<script setup>
import {nxhSDK} from "@/nxhsdk.module.min";
const store = nxhSDK.useSDKStore();
const appStyle = computed(() => store.state.appStyle);
</script>
<style lang="scss" scoped>
.good-box {
color: color(primary)
}
</style>
1. 页面盒子样式 (n-page)
使用方式
html
<view class="n-page">
<!-- 页面内容 -->
</view>
样式特点
- 动态内边距:通过
--style-padding
变量控制,后台暂无配置处,现在默认是12px - 灰色背景:
background-color: #F4F4F4
2. 卡片样式 (n-card)
使用方式
html
<view class="n-card">
<!-- 卡片内容 -->
</view>
样式特点
- 动态内边距:通过
--style-space
变量控制 - 白色背景:
background-color: #fff
- 圆角边框:
border-radius: 16rpx
- 底部间距:
margin-bottom: 16rpx
- 标准字体大小:
font-size: 28rpx
实际应用场景
在商品详情页中,n-card 被用于多个内容区块:
- 商品基本信息区
- 运费信息区
- 服务信息区
- 商品属性区
- 售后信息区
3. 按钮样式 (n-btn-)
系统提供两种主要的按钮样式,可以根据应用主题自动适应颜色和圆角。
实心按钮 (n-btn-primary)
主要用于强调操作,如"立即购买"按钮。
html
<view class="n-btn-primary">立即购买</view>
样式特点
- 动态圆角:通过
--style-radius
变量控制 - 动态边框颜色:使用主题色
--style-color-primary
- 动态背景颜色:使用主题色
--style-color-primary
- 文字颜色:白色
#fff
- 文字居中:
text-align: center
空心按钮 (n-btn-outline)
次要操作按钮,如"加入购物车"按钮。
html
<view class="n-btn-outline">加入购物车</view>
样式特点
- 动态圆角:与主按钮一致,通过
--style-radius
变量控制 - 动态边框颜色:使用主题色
--style-color-primary
- 动态文字颜色:使用主题色
--style-color-primary
- 背景颜色:白色
#fff
- 文字居中:
text-align: center
4. 标签样式 (n-tag)
系统提供线框风格、填充风格两种样式,可以根据应用主题自动适应。
使用方式
html
<view class="n-tag">
标签1
</view>
样式特点
4. 主题样式函数
系统提供了一系列 SCSS 函数和混合器,用于在样式中使用主题变量,保持样式的一致性和可维护性。
color() 函数
用于获取主题颜色变量,支持 primary 和 secondary 两种主色。
scss
// 使用方法
.my-element {
color: color(primary);
background-color: color(secondary);
}
// 编译后
.my-element {
color: var(--style-color-primary);
background-color: var(--style-color-secondary);
}
radius() 函数
用于获取主题圆角半径。
scss
// 使用方法
.my-element {
border-radius: radius();
}
// 编译后
.my-element {
border-radius: var(--style-radius);
}
space() 函数
用于获取主题间距变量。
scss
// 使用方法
.my-element {
padding: space();
}
// 编译后
.my-element {
padding: var(--style-space);
}
实际应用示例
scss
.price-tag {
color: color(primary);
border-radius: radius();
padding: space();
}
.product-card {
margin: space();
background-color: #fff;
border-radius: radius();
.title {
color: color(primary);
}
}
二、 固定辅助样式类
系统还提供了一系列辅助样式类,方便布局和间距调整:
1. 内外边距类样式
- 边距尺寸变量
- xs: 4px;
- sm: 8px;
- md: 12px;
- lg: 16px;
- xl: 20px;
- xxl: 24px;
- 外边距(margin)样式
- 左边距:
.ml-xs
,.ml-sm
,.ml-md
,.ml-lg
,.ml-xl
,.ml-xxl
- 右边距:
.mr-xs
,.mr-sm
,.mr-md
,.mr-lg
,.mr-xl
,.mr-xxl
- 上边距:
.mt-xs
,.mt-sm
,.mt-md
,.mt-lg
,.mt-xl
,.mt-xxl
- 下边距:
.mb-xs
,.mb-sm
,.mb-md
,.mb-lg
,.mb-xl
,.mb-xxl
- 水平边距:
.mx-xs
,.mx-sm
,.mx-md
,.mx-lg
,.mx-xl
,.mx-xxl
- 垂直边距:
.my-xs
,.my-sm
,.my-md
,.my-lg
,.my-xl
,.my-xxl
- 内边距(padding)样式
- 左内边距:
.pl-xs
,.pl-sm
,.pl-md
,.pl-lg
,.pl-xl
,.pl-xxl
- 右内边距:
.pr-xs
,.pr-sm
,.pr-md
,.pr-lg
,.pr-xl
,.pr-xxl
- 上内边距:
.pt-xs
,.pt-sm
,.pt-md
,.pt-lg
,.pt-xl
,.pt-xxl
- 下内边距:
.pb-xs
,.pb-sm
,.pb-md
,.pb-lg
,.pb-xl
,.pb-xxl
- 水平内边距:
.px-xs
,.px-sm
,.px-md
,.px-lg
,.px-xl
,.px-xxl
- 垂直内边距:
.py-xs
,.py-sm
,.py-md
,.py-lg
,.py-xl
,.py-xxl
这些样式可以直接在模板中使用,例如:
html
<view class="ml-lg">左边距16px</view>
<view class="pt-md pb-md">上下内边距各12px</view>
<view class="mx-xl my-sm">水平边距20px,垂直边距8px</view>
2. 字体大小(font-size)样式
系统提供了一套统一的字体大小样式类,从最小的 .fs-xs
到最大的 .fs-xxl
,方便在不同场景下使用合适的字号:
样式类名 | 字体大小 | 适用场景 |
---|---|---|
.fs-xxl | 20px | 重要数据展示 |
.fs-xl | 18px | 次级标题、适用于价格、详情页商品名称 |
.fs-lg | 16px | 小标题、重要内容 |
.fs-md | 14px | 正文内容、按钮文字 |
.fs-sm | 12px | 适用于标注次级信息、辅助说明文字 |
.fs-xs | 10px | 用于角标、备注信息、小标签内文字 |
使用示例:
html
<view class="fs-xxl">页面大标题 (20px)</view>
<view class="fs-xl">次级标题 (18px)</view>
<view class="fs-lg">小标题 (16px)</view>
<view class="fs-md">正文内容 (14px)</view>
<view class="fs-sm">辅助说明 (12px)</view>
<view class="fs-xs">备注信息 (10px)</view>
通过使用这些预定义的字体大小类,可以保持整个应用的字体大小一致性,提升用户体验。
三、 代码示例(商品详情页)
示例代码如下:
vue
<template>
<div class="detail-wrapper" :style="appStyle">
<scroll-view scroll-y="true" class="wrapper">
<swiper class="my-swipe" :indicator-dots="objGoods.album.length > 1" :autoplay="true" :interval="3000" circular>
<swiper-item class="my-swipe-item" v-for="(image, index) in objGoods.album" :key="index">
<img :src="image.file_url" alt="" class="my-swipe-img" @click="methods.imagePreview(objGoods.album,index)"/>
</swiper-item>
</swiper>
<view class="my-swipe" v-if="data.displayWay === 1">
<view class="my-swipe-item">
<img :src="objGoods.mobile_thumb" alt="" class="my-swipe-img"/>
</view>
</view>
<view class="card-list n-page">
<view class="goods-base-info n-card">
<view class="goods-title">
<text class="title">{{ showName ? objGoods.goods_name : '' }}</text>
</view>
<view class="goods-price">
<n-price v-if="data.showPrice" :price="objGoods.goods_amount" :color="styleJson.color1"/>
<text class="original-price" v-if="data.showOriginPrice && objGoods.market_amount">原价:{{ objGoods.market_amount }}元</text>
</view>
</view>
<view class="goods-block n-card" v-if="data.showFreight">
<view class="left-info">
<text class="label">运费</text>
<text class="value">¥{{ objGoods.delivery_amount }}</text>
</view>
<view class="right-info shippingfee" v-if="objGoods.attr && objGoods.attr.length && objGoods.attr[0].stock">剩余{{ objGoods.attr[0].stock }}件</view>
</view>
<view class="goods-block n-card" v-if="data.showService">
<view class="left-info">
<text class="label">服务</text>
<text class="value">快递发货 · 收货后结算</text>
</view>
<view class="right-info"><uni-icons type="right" size="16"></uni-icons></view>
</view>
<view class="goods-block multi-block n-card" v-if="data.showAttributes && objGoods.type?.length">
<view class="left-info" v-for="item in objGoods.type" :key="item.attr_value">
<text class="label" :class="{'label-multi':item.attr_name&&item.attr_name.length>=4}">{{ item.attr_name }}</text>
<text class="value">{{ item.attr_value }}</text>
</view>
</view>
<view class="goods-block n-card" v-if="data.showAfterSale">
<view class="left-info">
<text class="label">售后</text>
<text class="value">本商品由旗舰店销售,以订单...</text>
</view>
<view class="right-info"><uni-icons type="right" size="16"></uni-icons></view>
</view>
</view>
<view class="goods-detail" v-if="data.showDetail">
<view class="detail-content">
<rich-text :nodes="objGoods.context"></rich-text>
</view>
</view>
</scroll-view>
<view class="goods-btn-box">
<view class="goods-btn-mini">
<view class="btn-mini" @click="methods.showToast">
<image mode="widthFix" :src="`${cdnEnvUrl}images/user/car_v2.png`" class="icon-share"></image>
<text class="text">购物车</text>
<div class="num">1</div>
</view>
</view>
<view class="goods-btn-large">
<view class="btn-large n-btn-outline" @click="methods.showToast">加入购物车</view>
<view class="btn-large n-btn-primary ml-sm" @click="methods.toBuy">立即购买</view>
</view>
</view>
</div>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import {nxhSDK, nxhSDKLib} from "@/nxhsdk.module.min";
import { onLoad } from "@dcloudio/uni-app";
const id = ref(''); // 商品id
const data = reactive({
imgList: [],
imageH: '100vw',
goodsName: '',
displayWay: 0,
imgRatio: 0,
showName: true,
showPrice: true,
showOriginPrice: true,
showSoldOut: false,
showPreSale: false,
showFreight: true,
showService: false,
showAfterSale: false,
showReviews: false,
showStoreInfo: false,
showOfflineStore: false,
showDetail: true,
showMoreGoods: false,
content: '',
padding: 10,
gutter: 10,
imageRatio: 1,
listStyle: 0,
showAttributes:true,
})
const cdnEnvUrl = ref(uni.cdnEnvUrl);
const methods = reactive({
imagePreview: (imgList, index) => {
let urls = imgList.map(item => item.file_url);
uni.previewImage({
urls,
current: index
});
},
showToast: () => {
uni.showToast({
title: '敬请期待',icon: 'none'
})
},
toBuy: () => {
uni.navigateTo({
url: `/shop/order/confirm?id=${id.value}`
})
}
})
const store = nxhSDK.useSDKStore();
const appStyle = computed(() => store.state.appStyle);
const styleJson = computed(() => store.getters['getStyle']);
const { imageH, showName, showMoreGoods } = data
console.log(imageH,'imageH')
let objGoods = ref({})
const getGoodsDetail = async (id) => {
try {
uni.showLoading({
title: '加载中',
mask: true
})
const { result } = await nxhSDK.getData("shop_info_detail", { info_id: id})
objGoods.value = {
...result,
context: decodeURIComponent(result.context)
};
// 设置状态栏标题
uni.setNavigationBarTitle({
title: objGoods.value.goods_name || '商品详情'
})
console.warn('objGoods', objGoods.value);
} catch(err) {
console.error(err)
} finally {
uni.hideLoading()
}
}
onLoad((query)=>{
id.value = query.id
nxhSDKLib.$init(async () => {
getGoodsDetail(query.id)
})
})
</script>
<style lang="scss" scoped>
.detail-wrapper{
width: 100%;
height: 100vh;
.wrapper {
position: relative;
height: calc(100vh - 66px);
background: #F4F4F4;
max-width: 750px;
padding-bottom: 66px;
.my-swipe {
// height: 417px;
height: v-bind(imageH);
.my-swipe-item {
text-align: center;
.my-swipe-img {
width: 100%;
height: 100%;
height: v-bind(imageH);
object-fit: contain;
}
}
.custom-indicator {
position: absolute;
right: 5px;
bottom: 5px;
padding: 2px 5px;
font-size: 12px;
background: rgba(255,255,255,0.1);
border-radius: 38px;
}
}
.card-list {
display: flex;
flex-direction: column;
padding: 10px;
background-color: transparent;
margin-bottom: 0;
}
.goods-base-info {
display: flex;
flex-direction: column;
padding: 12px 0;
background-color: #fff;
border-radius: 8px;
.goods-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.title {
font-size: 16px;
font-weight: 600;
}
.btn-share {
font-size: 12px;
padding: 2px 8px;
background-color: color(secondary);
color: color(primary);
border-radius: 10px 0 0 10px;
}
}
.goods-price {
display: flex;
justify-content: space-between;
align-items: baseline;
.original-price {
text-decoration: line-through;
font-size: 14px;
color: #7C8FAC;
}
}
}
.goods-block {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
// margin-top: 8px;
font-size: 14px;
background-color: #fff;
border-radius: 8px;
.left-info {
display: flex;
.label {
display: inline-block;
width: 116rpx;
margin-right: 12px;
color: #969799;
text-align-last: justify;
}
.label-multi{
text-align-last:start;
}
.value {
color: #323233;
}
}
.right-info {
color: #969799;
}
.shippingfee{
color: #7C8FAC;
}
&.multi-block {
flex-direction: column;
align-items: flex-start;
.left-info:not(:last-of-type) {
margin-bottom: 8px;
}
}
}
.store-info-box {
display: flex;
padding: 14px 16px;
margin-top: 8px;
background-color: #fff;
.store-logo {
width: 56px;
height: 56px;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.store-info {
display: flex;
align-items: flex-start;
justify-content: space-between;
flex: 1;
margin-left: 16px;
.store-name {
font-size: 16px;
font-weight: 600;
}
.store-enter {
padding: 4px 8px;
font-size: 13px;
text-align: center;
color: color(primary);
border: 1px solid color(primary);
border-radius: 4px;
}
}
}
.goods-detail {
.detail-title {
padding: 16px;
font-size: 16px;
font-weight: 600;
}
.detail-content {
padding: 0;
img {
width: 100% !important;
}
}
}
.goods-more {
.more-title {
padding: 16px 16px 0;
font-size: 16px;
font-weight: 600;
}
}
}
.goods-btn-box {
// position: absolute;
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 750px;
padding: 24rpx 20rpx 32rpx 30rpx;
bottom: 0;
background-color: #fff;
box-sizing: border-box;
z-index: 999;
.goods-btn-mini {
display: grid;
grid-template-columns: 1fr;
.btn-mini {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 42px;
position: relative;
.num{
position: absolute;
top: -20rpx;
right: 6rpx;
border-radius: 50%;
width: 28rpx;
height: 28rpx;
background-color: color(primary);
border: 2rpx solid #FFFFFF;
color: #FFFFFF;
font-size: 16rpx;
text-align: center;
line-height: 28rpx;
}
image{
width: 29rpx;
height: 26rpx;
margin-bottom: 8rpx;
}
.text {
color: rgba(42, 53, 71, 1);
font-size: 11px;
}
}
}
.goods-btn-large {
display: flex;
color: #fff;
.btn-large {
width: 286rpx;
height: 76rpx;
line-height: 76rpx;
}
}
}
}
</style>