提交 a2bab482 authored 作者: 刘旭's avatar 刘旭

商城基础框架

上级
# Windows
[Dd]esktop.ini
Thumbs.db
$RECYCLE.BIN/
# macOS
.DS_Store
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
# Node.js
node_modules/
dist
.git
.idea
.hbuilderx
*.iml
pnpm-lock.yaml
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# mini-shop
#### Description
基于uniapp构建的小程序商城
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
# mini-shop
#### 介绍
基于uniapp构建的小程序商城,持续完善中...
#### 软件架构
uniapp(vue3) + vite + typescript + uni-ui
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
<!-- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title> -->
<!--preload-links-->
<!--app-context-->
<!-- </head>
<body>
<div id="app"> --><!--app-html--><!-- </div> -->
<!-- <script type="module" src="/src/main.ts"></script>
</body>
</html> -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!--preload-links-->
<!--app-context-->
<!-- 配置H5的 web图标static/logo.png -->
<link rel="icon" href="./static/logo.png" />
</head>
<body>
<div id="app">
<!--app-html-->
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-weixin": "uni -p mp-weixin",
"build:app": "uni build -p app",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-weixin": "uni build -p mp-weixin"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-components": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-h5": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3050320220729001",
"node-sass": "^7.0.1",
"pinia": "^2.0.21",
"vue": "^3.2.37",
"vue-i18n": "^9.1.9",
"vuex": "^4.0.2"
},
"devDependencies": {
"@dcloudio/types": "^3.0.13",
"@dcloudio/uni-automator": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3050320220729001",
"@dcloudio/uni-ui": "^1.4.20",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3050320220729001",
"@types/node": "^18.7.14",
"sass": "^1.54.8",
"sass-loader": "^13.0.2",
"typescript": "^4.7.4",
"vite": "^2.9.14"
}
}
<script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
onLaunch(() => {
console.log('App Launch');
});
onShow(() => {
console.log('App Show');
});
onHide(() => {
console.log('App Hide');
});
</script>
<style lang="scss">
@import './uni_modules/vk-uview-ui/index.scss';
page {
background-color: #f6f6f6;
}
</style>
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
import { createSSRApp } from 'vue';
import App from './App.vue';
import Model from '@/utils/util';
import uView from './uni_modules/vk-uview-ui';
export function createApp() {
const app = createSSRApp(App);
app.config.globalProperties.$model = Model;
app.use(uView);
return {
app
};
}
{
"name" : "",
"appid" : "__UNI__6D85B85",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios" : {},
/* SDK配置 */
"sdkConfigs" : {}
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "wx3c4c615b24d3b72b",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
{
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}, {
"path": "pages/category/index",
"style": {
"navigationBarTitleText": "分类"
}
}, {
"path": "pages/message/index",
"style": {
"navigationBarTitleText": "消息"
}
}, {
"path": "pages/message/chat",
"style": {
"navigationBarTitleText": "聊天窗口"
}
}, {
"path": "pages/cart/index",
"style": {
"navigationBarTitleText": "购物车"
}
}, {
"path": "pages/goods/index",
"style": {
"navigationBarTitleText": "商品详情"
}
}, {
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "我的"
}
}, {
"path": "pages/order/index",
"style": {
"navigationBarTitleText": "我的订单"
}
}
],
"tabBar": {
"selectedColor": "#333",
"color": "#707070",
"backgroundColor": "#fff",
"borderStyle": "white",
"height": "50px",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "/static/tabbar/home.png",
"selectedIconPath": "/static/tabbar/home_fill.png",
"text": "首页"
},
{
"pagePath": "pages/category/index",
"iconPath": "/static/tabbar/category.png",
"selectedIconPath": "/static/tabbar/category_fill.png",
"text": "分类"
},
{
"pagePath": "pages/message/index",
"iconPath": "/static/tabbar/message.png",
"selectedIconPath": "/static/tabbar/message_fill.png",
"text": "消息"
},
{
"pagePath": "pages/cart/index",
"iconPath": "/static/tabbar/cart.png",
"selectedIconPath": "/static/tabbar/cart_fill.png",
"text": "购物车"
},
{
"pagePath": "pages/profile/index",
"iconPath": "/static/tabbar/profile.png",
"selectedIconPath": "/static/tabbar/profile_fill.png",
"text": "我的"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
<template>
<view class="cart-container">
<view class="cart-item" v-for="(item, index) in cartList" :key="index">
<view class="item-infos">
<view class="item-title">
<view class="item-checkbox">
<checkbox-group @change="selectAll(item)">
<label>
<checkbox class="all-select" :checked="item.allChecked"/>
</label>
</checkbox-group>
</view>
<text>{{ item.shopName }}</text>
</view>
<view v-for="(goodsItem, goodsIndex) in item.goodsList" :key="goodsIndex">
<uni-swipe-action style="width: 100%">
<uni-swipe-action-item
:right-options="options"
:key="goodsItem.id"
@click="bindClick">
<view class="item-detail">
<view class="item-checkbox">
<checkbox-group @change="selected(goodsItem, item)">
<label>
<checkbox class="item-select" :checked="goodsItem.checked"/>
</label>
</checkbox-group>
</view>
<view class="goods-img">
<image :src="goodsItem.img"/>
</view>
<view class="goods-detail">
<view class="goods-title">
<text>{{ goodsItem.title }}</text>
</view>
<view class="goods-price-count">
<text class="goods-price">
<text class="unit"></text>
<text class="price">{{ goodsItem.price }}</text>
</text>
<view class="goods-count">
<uni-number-box
:value="goodsItem.selectCount"
:min="1"
:max="99"
@change="($value: number) => { changeBuyNum($value, goodsItem);}"/>
</view>
</view>
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</view>
</view>
<view class="bottom-bar">
<view class="bottom-left">
<view class="total-price-box">
<text>合计:</text>
<text class="goods-price">
<text class="unit"></text>
<text class="price">{{totalAmount}}</text>
</text>
</view>
</view>
<view class="bottom-right">
<text>结算</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import {reactive, ref, computed} from "vue";
import {CartItem} from "@/types/cart";
import {CartGoodsItem, GoodsItem} from "@/types/goods";
const cartList: CartItem[] = reactive([
{
cartId: 1,
shopName: 'XXX旗舰店',
goodsList: [
{
id: 1,
title: 'XXXXXXXXX',
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
price: '5500.00',
sales: '1000',
selectCount: 1
}, {
id: 2,
title: 'XXXXXXXXX',
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_2.jpg',
price: '5500.00',
sales: '1000',
selectCount: 1
}
]
}, {
cartId: 2,
shopName: 'XXX旗舰店',
goodsList: [
{
id: 1,
title: 'XXXXXXXXX',
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_3.jpg',
price: '5500.00',
sales: '1000',
selectCount: 1
}
]
}
])
const options = ref([
{
text: '删除',
style: {
backgroundColor: '#F56C6C'
}
}
])
const bindClick = (e: any) => {
console.log(e);
uni.showToast({
title: `点击了${e.position === 'left' ? '左侧' : '右侧'} ${e.content.text}按钮`,
icon: 'none'
});
}
const selectAll = (item: CartItem) => {
if (undefined === item.allChecked) {
item.allChecked = false
}
item.allChecked = !item.allChecked
item.goodsList.forEach(goods => {
goods.checked = item.allChecked;
})
}
const selected = (item: CartGoodsItem, cartItem: CartItem) => {
item.checked = !item.checked
let newArr: GoodsItem[] = cartItem.goodsList.filter(i => (i.checked === true))
cartItem.allChecked = newArr.length === cartItem.goodsList.length;
}
const changeBuyNum = (value: number, goodsItem: CartGoodsItem) => {
goodsItem.selectCount = value;
}
const totalAmount = computed(() => {
let totalAmount = 0;
cartList.forEach(cartItem => {
cartItem.goodsList.filter(goodsItem => (goodsItem.checked === true)).forEach(item => {
totalAmount = totalAmount + Number.parseFloat(item.price) * item.selectCount
})
})
return totalAmount.toFixed(2)
})
</script>
<style lang="scss" scoped>
.cart-container {
.cart-item {
margin: 20rpx;
border-radius: 20rpx;
background-color: #FFFFFF;
.item-infos {
padding: 20rpx;
//margin-left: 60rpx;
.item-title {
display: flex;
.item-checkbox {
.all-select {
transform: scale(0.7);
}
}
//margin-left: 60rpx;
}
.item-detail {
display: flex;
padding-top: 10rpx;
padding-bottom: 10rpx;
//line-height: 88rpx;
align-items: center;
.item-checkbox {
.item-select {
transform: scale(0.7);
}
}
.goods-img {
flex: 0;
image {
width: 100rpx;
height: 100rpx;
border-radius: 15rpx;
}
}
.goods-detail {
//display: flex;
flex: auto;
padding-left: 40rpx;
.goods-title {
}
.goods-price-count {
display: flex;
.goods-price {
color: #dd524d;
margin-top: 10rpx;
//position: absolute;
display: flex;
flex: auto;
.unit {
font-size: 10px;
}
.price {
font-size: 16px;
font-weight: bold
}
}
.goods-count {
margin-right: 25rpx;
}
}
}
}
}
}
//flex: auto;
.bottom-bar {
height: 50px;
background-color: #FFFFFF;
display: flex;
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin: 0;
.bottom-left {
flex: 2;
.total-price-box {
display: flex;
margin-left: 50rpx;
line-height: 100rpx;
font-weight: bold;
.goods-price {
color: #dd524d;
display: flex;
flex: auto;
.unit {
font-size: 10px;
}
.price {
font-size: 16px;
}
}
}
}
.bottom-right {
color: rgb(255, 255, 255);
display: flex;
flex: 1;
justify-content: center;
align-items: center;
background: linear-gradient(90deg, rgb(255, 138, 24), rgb(254, 96, 53));
}
}
}
</style>
<template>
分类
<button @tap="taps">dianji</button>
</template>
<script lang="ts" setup>
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
let taps = () => {
proxy?.$model.toast('加油')
}
</script>
<style lang="scss" scoped></style>
差异被折叠。
<template>
<view><uni-search-bar placeholder="输入搜索商品" bgColor="#EEEEEE" clearButton="auto" cancelButton="none" @blur="blur" @focus="focus" @confirm="search" /></view>
<view>
<swiper class="swiper" circular :indicator-dots="indicatorDots" :autoplay="autoplay" :interval="interval" :duration="duration">
<swiper-item v-for="(item, index) in bannerList" :key="index">
<view class="swiper-item"><image class="banner" :src="item.img" /></view>
</swiper-item>
</swiper>
</view>
<view>
<uni-grid :column="4" :showBorder="false">
<uni-grid-item v-for="(item, index) in gridList" :key="index">
<uni-icons :type="item.icon" :size="30" color="#777" />
<text class="text">{{ item.text }}</text>
</uni-grid-item>
</uni-grid>
</view>
<view class="goods">
<uni-row>
<uni-col :span="12" v-for="(item, index) in goodsList" :key="index">
<view class="goods-item" @click="viewGoods(item)">
<image class="goods-img" :src="item.img"></image>
<view class="goods-infos">
<uni-row>
<uni-col :span="24">
<text class="goods-title">{{ item.title }}</text>
</uni-col>
<uni-col>
<uni-row>
<uni-col :span="12">
<text class="goods-price">
<text class="unit"></text>
<text class="price">{{ item.price }}</text>
</text>
</uni-col>
<uni-col :span="12">
<text class="goods-sales-count">
<text>销量</text>
<text>{{ item.sales }}</text>
</text>
</uni-col>
</uni-row>
</uni-col>
</uni-row>
</view>
</view>
</uni-col>
</uni-row>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { GoodsItem } from '@/types/goods';
const indicatorDots = ref(true);
const autoplay = ref(true);
const interval = ref(3000);
const duration = ref(1500);
const bannerList = ref([
{
id: 1,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/banner/banner1.jpg'
},
{
id: 2,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/banner/banner2.jpg'
},
{
id: 3,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/banner/banner3.jpg'
}
]);
const gridList = ref([
{
icon: 'phone-filled',
text: '手机'
},
{
icon: 'phone',
text: '电脑'
},
{
icon: 'phone',
text: '家电'
},
{
icon: 'phone',
text: '更多'
}
]);
const goodsList = ref([
{
id: 1,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
title: 'Iphone 13 Pro Max 256G 全网通 国行正品',
price: '5000.00',
sales: 1000
},
{
id: 2,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_2.jpg',
title: 'Iphone 13 Pro Max',
price: '5000.00',
sales: 1000
},
{
id: 3,
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_3.jpg',
title: 'Iphone 13 Pro Max',
price: '5000.00',
sales: 1000
}
]);
const viewGoods = (row: GoodsItem) => {
console.log('查看商品', row.id);
uni.navigateTo({ url: '/pages/goods/index?goodsId=' + row.id });
};
</script>
<style lang="scss">
.swiper {
height: 300rpx;
.swiper-item {
display: block;
height: 300rpx;
line-height: 300rpx;
text-align: center;
.banner {
width: 100%;
height: 100%;
}
}
}
.text {
font-size: 14px;
margin-top: 5px;
}
.uni-grid-item__box {
align-items: center;
justify-content: center;
}
.goods-item {
width: auto;
height: 450rpx;
background-color: #ffffff;
margin: 5rpx;
//border: 1rpx solid #FFFFFF;
border-radius: 25rpx;
overflow: hidden;
.goods-img {
width: 100%;
height: 350rpx;
}
.goods-infos {
margin: 6rpx 10rpx;
.goods-title {
font-size: 16px;
font-family: arial, sans-serif;
display: -webkit-box;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.goods-price {
color: #dd524d;
font-family: arial, sans-serif;
.unit {
font-size: 10px;
}
.price {
font-size: 14px;
font-weight: bold;
}
}
.goods-sales-count {
//margin-right: 10rpx;;
text-align: right;
font-size: 10px;
color: #999999;
}
}
}
//.grid-item-box {
// flex: 1;
// // position: relative;
// /* #ifndef APP-NVUE */
// display: flex;
// /* #endif */
// flex-direction: column;
// align-items: center !important;
// justify-content: center !important;
// padding: 15px 0;
//}
</style>
差异被折叠。
<template>
<view class="message-container">
<view>
<uni-list :border="true">
<uni-swipe-action style="width: 100%">
<uni-swipe-action-item
v-for="(item, index) in chatList" :key="index"
:right-options="options"
@click="bindClick">
<!-- 头像显示角标 -->
<uni-list-chat
:title="item.nickname"
:avatar="item.avatar"
:note="item.lastMessage"
:time="item.createTime"
badge-positon="left"
:badge-text="item.unread"
clickable
@click="openChat(item)">
<!-- 此处可自定义内容 -->
</uni-list-chat>
</uni-swipe-action-item>
</uni-swipe-action>
</uni-list>
</view>
</view>
</template>
<script lang="ts" setup>
import {ref} from "vue";
const chatList = ref([
{
id: 1,
nickname: '娃哈哈',
avatar: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/logo/logo.png',
lastMessage: '在吗',
unread: '1',
createTime: '2022-09-17 21:33:00',
}, {
id: 2,
nickname: '康师傅',
avatar: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png',
lastMessage: '在吗',
unread: '1',
createTime: '2022-09-17 21:33:00',
}
])
const rightText = new Date().toLocaleTimeString()
const options = ref([
{
text: '删除',
style: {
backgroundColor: '#F56C6C'
}
}
])
const bindClick = (e: any) => {
console.log(e);
uni.showToast({
title: `点击了${e.position === 'left' ? '左侧' : '右侧'} ${e.content.text}按钮`,
icon: 'none'
});
}
const openChat = (item: any) => {
console.log(item)
uni.navigateTo({url: '/pages/message/chat?chatId=' + item.id})
}
</script>
<style scoped>
</style>
<template>
<view class="order-container">
<view class="order-search-box">
<uni-search-bar
placeholder="输入搜索订单"
bgColor="#EEEEEE"
clearButton="auto"
cancelButton="none"
@confirm="search"/>
</view>
<view class="order-type-tab">
<uni-segmented-control
ref="orderTypeTab"
:current="currentType"
:values="orderTypeTabs"
@clickItem="changeOrderType"
styleType="text"
activeColor="#f3a73f"></uni-segmented-control>
<view class="content">
<view class="order-content">
<view class="order-item" v-for="(item, index) in orderList" :key="index">
<view class="shop-name">
<text>{{ item.shopName }}</text>
</view>
<view class="order-goods-item" v-for="(goodsItem, goodsIndex) in item.goodsList" :key="goodsIndex">
<view class="goods-item">
<view class="goods-image">
<image :src="goodsItem.img"/>
</view>
<view class="goods-info">
<view class="goods-title-box">
<text class="goods-title">{{ goodsItem.title }}</text>
<text class="goods-spec">{{ goodsItem.spec }}</text>
</view>
</view>
<view class="goods-price">
<text>{{ goodsItem.price }}</text>
<text>x{{ goodsItem.num }}</text>
</view>
</view>
</view>
<view class="order-bottom">
<view class="total-price-box">
<text>实付款:</text>
<text class="goods-price">
<text class="unit"></text>
<text class="price">{{item.orderPrice}}</text>
</text>
</view>
<view class="option-btn">
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import {ref} from "vue";
import {onLoad} from "@dcloudio/uni-app";
let currentType = ref(0)
onLoad((options: any) => {
currentType.value = Number.parseInt(options.orderType) + 1
})
const orderTypeTabs = ref(['全部', '待付款', '待发货', '待收货', '待评价'])
const orderList = ref([
{
shopName: 'Apple官方旗舰店',
orderStatus: '待付款',
orderPrice: '11000',
goodsList: [
{
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
title: 'Iphone 13 Pro Max',
spec: '石墨色',
price: '6000',
num: '1',
realPrice: '5500'
}, {
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
title: 'Iphone 13 Pro Max 256G 全网通 国行正品',
spec: '石墨色',
price: '6000',
num: '1',
realPrice: '5500'
}
]
}, {
shopName: 'Apple官方旗舰店',
orderStatus: '待付款',
orderPrice: '11000',
goodsList: [
{
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
title: 'Iphone 13 Pro Max',
spec: '石墨色',
price: '6000',
num: '1',
realPrice: '5500'
}, {
img: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/goods/goods_1.jpg',
title: 'Iphone 13 Pro Max 256G 全网通 国行正品',
spec: '石墨色',
price: '6000',
num: '1',
realPrice: '5500'
}
]
}
])
const search = (e: any) => {
const keyword = e.value
}
const changeOrderType = (e: any) => {
currentType.value = e.currentIndex
}
</script>
<style lang="scss" scoped>
.order-container {
.order-search-box {
}
.order-type-tab {
.content {
.order-content {
.order-item {
//height: 300rpx;
//margin: 20rpx 0;
margin-top: 20rpx;
padding: 20rpx 30rpx;
border-radius: 20rpx;
background-color: #FFFFFF;
.shop-name {
//padding: 0 0 0 30rpx;
}
.order-goods-item {
//padding: 10rpx 0 0 30rpx;
padding-top: 10rpx;
.goods-item {
display: flex;
.goods-image {
//width: 130rpx;
//height: 130rpx;
min-height: 130rpx;
min-width: 130rpx;
max-height: 130rpx;
max-width: 130rpx;
border-radius: 20rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.goods-info {
padding-left: 15rpx;
flex: auto;
.goods-title-box {
text-align: left;
.goods-title {
display: -webkit-box;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
}
}
.goods-price {
flex: 1;
display: grid;
text-align: right;
right: 0;
text {
}
}
}
}
.order-bottom {
border-top: #e9e9eb 1px solid;
.total-price-box {
display: flex;
line-height: 100rpx;
font-weight: bold;
.goods-price {
color: #dd524d;
display: flex;
flex: auto;
.unit {
font-size: 10px;
}
.price {
font-size: 16px;
}
}
}
.option-btn {
}
}
}
}
}
}
}
</style>
<template>
<view class="profile-container">
<view class="profile-info">
<view class="profile-avatar">
<image src="https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/logo/logo.png"/>
</view>
<view class="profile-nickname">
<text>Mr Tsui</text>
</view>
</view>
<view class="order-center">
<uni-section title="我的订单" type="line" padding>
<template v-slot:right>
<view class="order-more" @click="showAllOrder">全部</view>
</template>
<uni-grid :column="5" :show-border="false" :square="false" @change="handleOrderTypeChange">
<uni-grid-item v-for="(item ,index) in orderBtnList" :index="index" :key="index">
<view class="grid-item-box">
<image class="image" :src="item.url" mode="aspectFill" />
<text class="text">{{item.text}}</text>
<view v-if="item.badge" class="grid-dot">
<uni-badge :text="item.badge" :type="item.type" />
</view>
</view>
</uni-grid-item>
</uni-grid>
</uni-section>
</view>
<view class="function-expand">
<uni-grid :column="4" :show-border="false" :square="false" @change="change">
<uni-grid-item v-for="(item ,index) in expandBtnList" :index="index" :key="index">
<view class="grid-item-box">
<image class="image" :src="item.url" mode="aspectFill" />
<text class="text">{{item.text}}</text>
<view v-if="item.badge" class="grid-dot">
<uni-badge :text="item.badge" :type="item.type" />
</view>
</view>
</uni-grid-item>
</uni-grid>
</view>
<view class="function-list">
<uni-list>
<!-- <uni-list-item :show-extra-icon="true" showArrow :extra-icon="extraIcon" title="列表左侧带扩展图标"/>-->
<uni-list-item
title="个人中心"
showArrow
thumb="https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/address_dizhi.png"
thumb-size="default"/>
<uni-list-item
title="设置"
showArrow
thumb="https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/address_dizhi.png"
thumb-size="default"/>
<uni-list-item
title="关于"
showArrow
thumb="https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/address_dizhi.png"
thumb-size="default"/>
</uni-list>
</view>
</view>
</template>
<script lang="ts" setup>
import {ref} from "vue";
const orderBtnList = ref([
{
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/payment.png',
text: '待付款',
badge: 1,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daifahuo.png',
text: '待发货',
badge: 1,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daishouhuo.png',
text: '待收货',
badge: 1,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daipingjia.png',
text: '待评价',
badge: 1,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/shouhou.png',
text: '售后',
badge: 1,
type: 'error'
}
])
const expandBtnList = ref([
{
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/payment.png',
text: '我的收藏',
badge: 0,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daifahuo.png',
text: '我的足迹',
badge: 0,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daishouhuo.png',
text: '收货地址',
badge: 0,
type: 'error'
}, {
url: 'https://bytelibs-dev.oss-cn-beijing.aliyuncs.com/image/order/daipingjia.png',
text: '我的评价',
badge: 0,
type: 'error'
}
])
const handleOrderTypeChange = (e: any) => {
const index = e.detail.index
uni.navigateTo({url: '/pages/order/index?orderType=' + index})
// switch (index) {
// case 0:
// console.log('待付款')
// break;
// case 1:
// console.log('待发货')
// break;
// case 2:
// console.log('待收货')
// break;
// case 3:
// console.log('待评价')
// break;
// case 4:
// console.log('售后')
// break;
// default:
// break;
// }
}
const showAllOrder = () => {
uni.navigateTo({url: '/pages/order/index'})
}
</script>
<style lang="scss" scoped>
.profile-container {
.profile-info {
height: 200rpx;
margin-top: 20rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
display: flex;
.profile-avatar {
padding: 50rpx 40rpx;
image {
width: 100rpx;
height: 100rpx;
overflow: hidden;
border-radius: 50%;
}
}
.profile-nickname {
flex: auto;
margin-top: 50rpx;
margin-left: 10rpx;
text {
font-weight: bold;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
}
}
}
.order-center, .function-expand {
margin-top: 15rpx;
border-radius: 20rpx;
background-color: #FFFFFF;
.order-more {
margin-right: 20rpx;
}
.grid-item-box {
font-size: 24rpx;
flex: 1;
// position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10rpx 0;
.grid-item-box-row {
flex: 1;
// position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
//padding: 15px 0;
}
.grid-dot {
position: absolute;
top: -5rpx;
right: 25rpx;
}
}
}
.function-expand {
padding: 15rpx 0;
//font-size: 24rpx;
}
.function-list {
padding-top: 15rpx;
border-radius: 20rpx;
}
}
.image {
width: 25px;
height: 25px;
}
</style>
<template>
</template>
<script>
export default {
name: "index"
}
</script>
<style scoped>
</style>
import { defineStore, createPinia } from 'pinia'
import {CartGoodsItem} from "@/types/goods";
export class CartItem {
cartId: number;
shopName: string;
allChecked?: boolean = false;
goodsList: CartGoodsItem[]
constructor(cartId: number, shopName: string, allChecked: boolean = false, goodsList: CartGoodsItem[]) {
this.cartId = cartId;
this.shopName = shopName;
this.allChecked = allChecked;
this.goodsList = goodsList;
}
}
export class GoodsItem {
id: number;
img: string;
title: string;
price: string;
sales: string;
constructor(id: number, img: string, title: string, price: string, sales: string) {
this.id = id;
this.img = img;
this.title = title;
this.price = price;
this.sales = sales;
}
}
export class CartGoodsItem {
id: number;
img: string;
title: string;
price: string;
sales: string;
checked?: boolean;
selectCount: number;
constructor(id: number, img: string, title: string, price: string, sales: string, checked: boolean = true, selectCount: number) {
this.id = id;
this.img = img;
this.title = title;
this.price = price;
this.sales = sales;
this.checked = checked;
this.selectCount = selectCount;
}
}
@import '../node_modules/@dcloudio/uni-ui/lib/uni-scss/variables.scss';
@import "@/uni_modules/vk-uview-ui/theme.scss";
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:24rpx;
$uni-font-size-base:28rpx;
$uni-font-size-lg:32rpx;
/* 图片尺寸 */
$uni-img-size-sm:40rpx;
$uni-img-size-base:52rpx;
$uni-img-size-lg:80rpx;
/* Border Radius */
$uni-border-radius-sm: 4rpx;
$uni-border-radius-base: 6rpx;
$uni-border-radius-lg: 12rpx;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 10px;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
/* 垂直间距 */
$uni-spacing-col-sm: 8rpx;
$uni-spacing-col-base: 16rpx;
$uni-spacing-col-lg: 24rpx;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:40rpx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:36rpx;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:30rpx;
## 1.4.0(2022-10-07)
* 【修复】`u-section` 点击更多,会触发两次事件的问题
* 【修复】`loadMore` 加载更多,icon-type为circle不会转动的问题
* 【修复】`u-subsection` current 有转入值时,变更值,样式不更新(需用 `v-model="current"` 代替 `:current="current"`
## 1.3.13(2022-09-28)
* 【修复】`u-avatar-cropper` 组件在vue3中会报错的问题。
## 1.3.12(2022-08-30)
* 【优化】`u-keyboard` 组件内部细节。
## 1.3.11(2022-08-30)
* 【修复】`u-subsection` 组件的 `list` 属性不支持动态修改的问题。
## 1.3.10(2022-07-30)
* 【优化】上传组件部分细节
## 1.3.9(2022-07-07)
* 【更新】省市区数据源
* 【优化】`u-subsection` 组件支持在右上角显示数字角标
```html
<template>
<u-subsection :list="list"></u-subsection>
</template>
```
```js
export default {
data() {
return {
list: [
{
name: '待发货',
num: 10
},
{
name: '待付款',
num: 5
},
{
name: '待评价',
num: 15
}
]
}
}
}
```
## 1.3.8(2022-06-13)
* 【优化】组件 `u-icon`,使之更方便的兼容第三方icon(满足规则自动计算customPrefix)
**规则如下:**
*`name` 中包含 `-icon-` 字符串时
*`vk-icon-goods`,则组件的 `customPrefix` 属性自动识别为 `vk-icon``name`属性 自动识别为 `goods`
*`vk-2-icon-goods-list`,则组件的 `customPrefix` 属性自动识别为 `vk-2-icon``name`属性 自动识别为 `goods-list`
## 1.3.7(2022-06-10)
* 【优化】组件 `u-action-sheet` `u-calendar` `u-dropdown-item` `u-field` `u-input` `u-keyboard` `u-modal` `u-radio-group` `u-rate` `u-search` `u-slider` `u-switch` `u-tabbar` `u-waterfall``vue3` 模式下的细节问题。
## 1.3.6(2022-06-10)
* 【优化】组件 `u-action-sheet` `u-calendar` `u-dropdown-item` `u-field` `u-input` `u-keyboard` `u-modal` `u-radio-group` `u-rate` `u-search` `u-slider` `u-switch` `u-tabbar` `u-waterfall``vue3` 模式下的细节问题。
## 1.3.5(2022-05-28)
* 【优化】组件 `u-mask` `u-popup` `u-select` `u-modal` `u-keyboard` `u-calendar` `u-action-sheet` `u-picker` 均新增 `blur` 属性,可用于设置弹出遮罩的模糊度,默认为0(不模糊)
* ![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-cf0c5e69-620c-4f3c-84ab-f4619262939f/49b773a3-273f-4b1c-95e8-a42dcba1a53c.png)
## 1.3.4(2022-05-03)
* 【修复】`u-tabs` 组件细节问题。
## 1.1.4(2022-03-22)
* 【修复】`u-field` 组件 `arrowDirection` 属性无效的问题。
## 1.1.3(2022-03-21)
* 【优化】部分细节。
## 1.1.2(2022-03-21)
* 【优化】部分细节。
## 1.1.1(2022-03-17)
* 【优化】部分细节。
## 1.1.0(2022-03-12)
* 【重要】`u-picker` 组件新增 `regionDiscern` 方法 智能识别省市区街道地址
如将字符串 `浙江省杭州市西湖区希望路1333弄是啊我庭12号楼1203` 中识别为
```json
{
"province": {
"code": "330000",
"name": "浙江省"
},
"city": {
"code": "330100",
"name": "杭州市"
},
"area": {
"code": "330106",
"name": "西湖区"
},
"address": "龙井路1号",
"formatted_address": "浙江省杭州市西湖区龙井路1号"
}
```
而组件的 `addressDiscern` 方法还可以识别收货信息,如 `张三 13888888888 上海市嘉定区希望路1333弄是啊我庭12号楼1203` 中识别姓名、手机号、地址(支持多种格式)
## 1.0.13(2022-03-12)
* 【优化】部分细节。
## 1.0.12(2022-03-09)
* 【修复】`u-radio-group` 在 vue3 模式下,设置默认值可能会无效的问题。
## 1.0.11(2022-03-07)
* 【优化】部分细节。
## 1.0.10(2022-03-05)
* 【修复】`u-radio` 中的值相等的判断 == 改为 ===
* 【优化】部分注释的错别字。
## 1.0.9(2022-03-03)
* 【修复】`u-parse` 在 vue3模式下编译到app无法正常显示的问题。
## 1.0.8(2022-02-26)
* 【优化】`u-form` 组件新增2个属性 `inputAlign``clearable` 用于统一设置表单内所有 `u-input` 组件的对应属性默认值
* 【优化】更新城市数据源信息
## 1.0.7(2022-02-25)
* 【重要】`u-picker` 组件新增 `addressDiscern` 方法 智能识别收货信息
如在 `张三 13888888888 上海市嘉定区希望路1333弄是啊我庭12号楼1203` 中识别姓名、手机号、地址(支持多种格式)
即使这样的字符串也能识别 `!!!!~~~$张三~~~上海市嘉定区希望路1333弄是啊我庭12号楼1203【【【【13888888888】`
## 1.0.6(2022-02-24)
* 【优化】`u-form-item` 组件的 `prop` 属性支持 a.b 形式
## 1.0.5(2022-01-11)
* 【修复】`u-sticky` 组件 在微信小程序中无法正常吸顶的问题
## 1.0.4(2021-12-31)
* 【优化】`u-dropdown-item` 组件 0和"" 无法区分的问题。
* 【修复】`u-modal` 在Vue3版本中使用了mask-close-able属性无效的问题
## 1.0.3(2021-12-20)
【优化】u-icon在微信小程序下可能会显示null字符串的问题
## 1.0.2(2021-12-09)
* 1、【优化】`u-button` 组件新增 `timerId` 属性
* 之前的效果是:所有按钮一定时间内只能点击1次(`共用计算时间`)导致点击按钮A后无法马上点击按钮B
* 优化的效果是:每个按钮一定时间内只能点击1次(`分开计算时间`)且支持设置相同的 timerId 来达到指定按钮 `共用计算时间`
## 1.0.1(2021-11-22)
* 修复 u-parse 组件在微信小程序上的显示问题。
## 1.0.0(2021-11-18)
uView Vue3.0 横空出世,继承uView1.0意志,再战江湖,风云再起!by vk 2021-11-18
<template>
<u-popup
:blur="blur"
mode="bottom"
:border-radius="borderRadius"
:popup="false"
v-model="popupValue"
:maskCloseAble="maskCloseAble"
length="auto"
:safeAreaInsetBottom="safeAreaInsetBottom"
@close="popupClose"
:z-index="uZIndex"
>
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
<text>{{ tips.text }}</text>
</view>
<block v-for="(item, index) in list" :key="index">
<view
@touchmove.stop.prevent
@tap="itemClick(index)"
:style="[itemStyle(index)]"
class="u-action-sheet-item u-line-1"
:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
:hover-stay-time="150"
>
<text>{{ item[labelName] }}</text>
<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">
{{ item.subText }}
</text>
</view>
</block>
<view class="u-gab" v-if="cancelBtn"></view>
<view
@touchmove.stop.prevent
class="u-actionsheet-cancel u-action-sheet-item"
hover-class="u-hover-class"
:hover-stay-time="150"
v-if="cancelBtn"
@tap="close"
>
{{ cancelText }}
</view>
</u-popup>
</template>
<script>
/**
* actionSheet 操作菜单
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
* @tutorial https://www.uviewui.com/components/actionSheet.html
* @property {Array<Object>} list 按钮的文字数组,见官方文档示例
* @property {Object} tips 顶部的提示文字,见官方文档示例
* @property {String} cancel-text 取消按钮的提示文字
* @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
* @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {Number String} z-index z-index值(默认1075)
* @property {String} cancel-text 取消按钮的提示文字
* @event {Function} click 点击ActionSheet列表项时触发
* @event {Function} close 点击取消按钮时触发
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
*/
export default {
name: "u-action-sheet",
emits: ["update:modelValue", "input", "click", "close"],
props: {
// 通过双向绑定控制组件的弹出与收起
value: {
type: Boolean,
default: false
},
modelValue: {
type: Boolean,
default: false
},
// 点击遮罩是否可以关闭actionsheet
maskCloseAble: {
type: Boolean,
default: true
},
// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
list: {
type: Array,
default() {
// 如下
// return [{
// text: '确定',
// color: '',
// fontSize: ''
// }]
return [];
}
},
// 顶部的提示文字
tips: {
type: Object,
default() {
return {
text: "",
color: "",
fontSize: "26"
};
}
},
// 底部的取消按钮
cancelBtn: {
type: Boolean,
default: true
},
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// 弹出的顶部圆角值
borderRadius: {
type: [String, Number],
default: 0
},
// 弹出的z-index值
zIndex: {
type: [String, Number],
default: 0
},
// 取消按钮的文字提示
cancelText: {
type: String,
default: "取消"
},
// 自定义label属性名
labelName: {
type: String,
default: "text"
},
// 遮罩的模糊度
blur: {
type: [Number, String],
default: 0
}
},
computed: {
valueCom() {
// #ifndef VUE3
return this.value;
// #endif
// #ifdef VUE3
return this.modelValue;
// #endif
},
// 顶部提示的样式
tipsStyle() {
let style = {};
if (this.tips.color) style.color = this.tips.color;
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + "rpx";
return style;
},
// 操作项目的样式
itemStyle() {
return index => {
let style = {};
if (this.list[index].color) style.color = this.list[index].color;
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + "rpx";
// 选项被禁用的样式
if (this.list[index].disabled) style.color = "#c0c4cc";
return style;
};
},
uZIndex() {
// 如果用户有传递z-index值,优先使用
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
data() {
return {
popupValue: false
};
},
watch: {
valueCom(v1, v2) {
this.popupValue = v1;
}
},
methods: {
// 点击取消按钮
close() {
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
// 这是一个vue发送事件的特殊用法
this.popupClose();
this.$emit("close");
},
// 弹窗关闭
popupClose() {
this.$emit("input", false);
this.$emit("update:modelValue", false);
},
// 点击某一个item
itemClick(index) {
// disabled的项禁止点击
if (this.list[index].disabled) return;
this.$emit("click", index);
this.$emit("input", false);
this.$emit("update:modelValue", false);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-tips {
font-size: 26rpx;
text-align: center;
padding: 34rpx 0;
line-height: 1.5;
color: $u-tips-color;
}
.u-action-sheet-item {
@include vue-flex;
line-height: 1;
justify-content: center;
align-items: center;
font-size: 32rpx;
padding: 34rpx 0;
flex-direction: column;
}
.u-action-sheet-item__subtext {
font-size: 24rpx;
color: $u-tips-color;
margin-top: 20rpx;
}
.u-gab {
height: 12rpx;
background-color: rgb(234, 234, 236);
}
.u-actionsheet-cancel {
color: $u-main-color;
}
</style>
<template>
<view class="u-alert-tips" v-if="show" :class="[
!show ? 'u-close-alert-tips': '',
type ? 'u-alert-tips--bg--' + type + '-light' : '',
type ? 'u-alert-tips--border--' + type + '-disabled' : '',
]" :style="{
backgroundColor: bgColor,
borderColor: borderColor
}">
<view class="u-icon-wrap">
<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
</view>
<view class="u-alert-content" @tap.stop="click">
<view class="u-alert-title" :style="[uTitleStyle]">
{{title}}
</view>
<view v-if="description" class="u-alert-desc" :style="[descStyle]">
{{description}}
</view>
</view>
<view class="u-icon-wrap">
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
:size="22" class="u-close-icon" :style="{
top: description ? '18rpx' : '24rpx'
}"></u-icon>
</view>
<text v-if="closeAble && closeText" class="u-close-text" :style="{
top: description ? '18rpx' : '24rpx'
}">{{closeText}}</text>
</view>
</template>
<script>
/**
* alertTips 警告提示
* @description 警告提示,展现需要关注的信息
* @tutorial https://uviewui.com/components/alertTips.html
* @property {String} title 显示的标题文字
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
* @property {String} type 关闭按钮(默认为叉号icon图标)
* @property {String} icon 图标名称
* @property {Object} icon-style 图标的样式,对象形式
* @property {Object} title-style 标题的样式,对象形式
* @property {Object} desc-style 描述的样式,对象形式
* @property {String} close-able 用文字替代关闭图标,close-able为true时有效
* @property {Boolean} show-icon 是否显示左边的辅助图标
* @property {Boolean} show 显示或隐藏组件
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
*/
export default {
name: 'u-alert-tips',
emits: ["click", "close"],
props: {
// 显示文字
title: {
type: String,
default: ''
},
// 主题,success/warning/info/error
type: {
type: String,
default: 'warning'
},
// 辅助性文字
description: {
type: String,
default: ''
},
// 是否可关闭
closeAble: {
type: Boolean,
default: false
},
// 关闭按钮自定义文本
closeText: {
type: String,
default: ''
},
// 是否显示图标
showIcon: {
type: Boolean,
default: false
},
// 文字颜色,如果定义了color值,icon会失效
color: {
type: String,
default: ''
},
// 背景颜色
bgColor: {
type: String,
default: ''
},
// 边框颜色
borderColor: {
type: String,
default: ''
},
// 是否显示
show: {
type: Boolean,
default: true
},
// 左边显示的icon
icon: {
type: String,
default: ''
},
// icon的样式
iconStyle: {
type: Object,
default() {
return {}
}
},
// 标题的样式
titleStyle: {
type: Object,
default() {
return {}
}
},
// 描述文字的样式
descStyle: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
}
},
computed: {
uTitleStyle() {
let style = {};
// 如果有描述文字的话,标题进行加粗
style.fontWeight = this.description ? 500 : 'normal';
// 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖
return this.$u.deepMerge(style, this.titleStyle);
},
uIcon() {
// 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标
return this.icon ? this.icon : this.$u.type2icon(this.type);
},
uIconType() {
// 如果有设置图标的样式,优先使用,没有的话,则用type的样式
return Object.keys(this.iconStyle).length ? '' : this.type;
}
},
methods: {
// 点击内容
click() {
this.$emit('click');
},
// 点击关闭按钮
close() {
this.$emit('close');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-alert-tips {
@include vue-flex;
align-items: center;
padding: 16rpx 30rpx;
border-radius: 8rpx;
position: relative;
transition: all 0.3s linear;
border: 1px solid #fff;
&--bg--primary-light {
background-color: $u-type-primary-light;
}
&--bg--info-light {
background-color: $u-type-info-light;
}
&--bg--success-light {
background-color: $u-type-success-light;
}
&--bg--warning-light {
background-color: $u-type-warning-light;
}
&--bg--error-light {
background-color: $u-type-error-light;
}
&--border--primary-disabled {
border-color: $u-type-primary-disabled;
}
&--border--success-disabled {
border-color: $u-type-success-disabled;
}
&--border--error-disabled {
border-color: $u-type-error-disabled;
}
&--border--warning-disabled {
border-color: $u-type-warning-disabled;
}
&--border--info-disabled {
border-color: $u-type-info-disabled;
}
}
.u-close-alert-tips {
opacity: 0;
visibility: hidden;
}
.u-icon {
margin-right: 16rpx;
}
.u-alert-title {
font-size: 28rpx;
color: $u-main-color;
}
.u-alert-desc {
font-size: 26rpx;
text-align: left;
color: $u-content-color;
}
.u-close-icon {
position: absolute;
top: 20rpx;
right: 20rpx;
}
.u-close-hover {
color: red;
}
.u-close-text {
font-size: 24rpx;
color: $u-tips-color;
position: absolute;
top: 20rpx;
right: 20rpx;
line-height: 1;
}
</style>
<template>
<view class="content">
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
<canvas
class="cropper"
:disable-scroll="true"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
canvas-id="cropper"
id="cropper"
></canvas>
<canvas
class="cropper"
:disable-scroll="true"
:style="{
position: 'fixed',
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
height: `${cropperOpt.height * cropperOpt.pixelRatio}`
}"
canvas-id="targetId"
id="targetId"
></canvas>
</view>
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
<!-- #ifdef H5 -->
<view class="upload" @tap="uploadTap">选择图片</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="upload" @tap="uploadTap">重新选择</view>
<!-- #endif -->
<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
</view>
</view>
</template>
<script>
import WeCropper from './weCropper.js';
export default {
props: {
// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
boundStyle: {
type: Object,
default() {
return {
lineWidth: 4,
borderColor: 'rgb(245, 245, 245)',
mask: 'rgba(0, 0, 0, 0.35)'
};
}
}
// // 裁剪框宽度,单位rpx
// rectWidth: {
// type: [String, Number],
// default: 400
// },
// // 裁剪框高度,单位rpx
// rectHeight: {
// type: [String, Number],
// default: 400
// },
// // 输出图片宽度,单位rpx
// destWidth: {
// type: [String, Number],
// default: 400
// },
// // 输出图片高度,单位rpx
// destHeight: {
// type: [String, Number],
// default: 400
// },
// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
// fileType: {
// type: String,
// default: 'jpg',
// },
// // 生成的图片质量
// // H5上无效,目前不考虑使用此参数
// quality: {
// type: [Number, String],
// default: 1
// }
},
data() {
return {
// 底部导航的高度
bottomNavHeight: 50,
originWidth: 200,
width: 0,
height: 0,
cropperOpt: {
id: 'cropper',
targetId: 'targetCropper',
pixelRatio: 1,
width: 0,
height: 0,
scale: 2.5,
zoom: 8,
cut: {
x: (this.width - this.originWidth) / 2,
y: (this.height - this.originWidth) / 2,
width: this.originWidth,
height: this.originWidth
},
boundStyle: {
lineWidth: uni.upx2px(this.boundStyle.lineWidth),
mask: this.boundStyle.mask,
color: this.boundStyle.borderColor
}
},
// 裁剪框和输出图片的尺寸,高度默认等于宽度
// 输出图片宽度,单位px
destWidth: 200,
// 裁剪框宽度,单位px
rectWidth: 200,
// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
fileType: 'jpg',
src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
};
},
onLoad(option) {
let rectInfo = uni.getSystemInfoSync();
this.width = rectInfo.windowWidth;
this.height = rectInfo.windowHeight - this.bottomNavHeight;
this.cropperOpt.width = this.width;
this.cropperOpt.height = this.height;
this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
if (option.destWidth) this.destWidth = option.destWidth;
if (option.rectWidth) {
let rectWidth = Number(option.rectWidth);
this.cropperOpt.cut = {
x: (this.width - rectWidth) / 2,
y: (this.height - rectWidth) / 2,
width: rectWidth,
height: rectWidth
};
}
this.rectWidth = option.rectWidth;
if (option.fileType) this.fileType = option.fileType;
// 初始化
this.cropper = new WeCropper(this.cropperOpt)
.on('ready', ctx => {
// wecropper is ready for work!
})
.on('beforeImageLoad', ctx => {
// before picture loaded, i can do something
})
.on('imageLoad', ctx => {
// picture loaded
})
.on('beforeDraw', (ctx, instance) => {
// before canvas draw,i can do something
});
// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
uni.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: '#000000'
});
uni.chooseImage({
count: 1, // 默认9
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: res => {
this.src = res.tempFilePaths[0];
// 获取裁剪图片资源后,给data添加src属性及其值
this.cropper.pushOrign(this.src);
}
});
},
methods: {
touchStart(e) {
this.cropper.touchStart(e);
},
touchMove(e) {
this.cropper.touchMove(e);
},
touchEnd(e) {
this.cropper.touchEnd(e);
},
getCropperImage(isPre = false) {
if(!this.src) return this.$u.toast('请先选择图片再裁剪');
let cropper_opt = {
destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
destWidth: Number(this.destWidth),
fileType: this.fileType
};
this.cropper.getCropperImage(cropper_opt, (path, err) => {
if (err) {
uni.showModal({
title: '温馨提示',
content: err.message
});
} else {
if (isPre) {
uni.previewImage({
current: '', // 当前显示图片的 http 链接
urls: [path] // 需要预览的图片 http 链接列表
});
} else {
uni.$emit('uAvatarCropper', path);
this.$u.route({
type: 'back'
});
}
}
});
},
uploadTap() {
const self = this;
uni.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
self.src = res.tempFilePaths[0];
// 获取裁剪图片资源后,给data添加src属性及其值
self.cropper.pushOrign(this.src);
}
});
}
}
};
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.content {
background: rgba(255, 255, 255, 1);
}
.cropper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11;
}
.cropper-buttons {
background-color: #000000;
color: #eee;
}
.cropper-wrapper {
position: relative;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #000;
}
.cropper-buttons {
width: 100vw;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
font-size: 28rpx;
}
.cropper-buttons .upload,
.cropper-buttons .getCropperImage {
width: 50%;
text-align: center;
}
.cropper-buttons .upload {
text-align: left;
padding-left: 50rpx;
}
.cropper-buttons .getCropperImage {
text-align: right;
padding-right: 50rpx;
}
</style>
<template>
<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
bottom: bottom + 'rpx',
right: right + 'rpx',
borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
zIndex: uZIndex,
opacity: opacity
}, customStyle]">
<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
<view class="u-back-top__content__tips">
{{tips}}
</view>
</view>
<slot v-else />
</view>
</template>
<script>
export default {
name: 'u-back-top',
props: {
// 返回顶部的形状,circle-圆形,square-方形
mode: {
type: String,
default: 'circle'
},
// 自定义图标
icon: {
type: String,
default: 'arrow-upward'
},
// 提示文字
tips: {
type: String,
default: ''
},
// 返回顶部滚动时间
duration: {
type: [Number, String],
default: 100
},
// 滚动距离
scrollTop: {
type: [Number, String],
default: 0
},
// 距离顶部多少距离显示,单位rpx
top: {
type: [Number, String],
default: 400
},
// 返回顶部按钮到底部的距离,单位rpx
bottom: {
type: [Number, String],
default: 200
},
// 返回顶部按钮到右边的距离,单位rpx
right: {
type: [Number, String],
default: 40
},
// 层级
zIndex: {
type: [Number, String],
default: '9'
},
// 图标的样式,对象形式
iconStyle: {
type: Object,
default() {
return {
color: '#909399',
fontSize: '38rpx'
}
}
},
// 整个组件的样式
customStyle: {
type: Object,
default() {
return {}
}
}
},
watch: {
showBackTop(nVal, oVal) {
// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
if(nVal) {
this.uZIndex = this.zIndex;
this.opacity = 1;
} else {
this.uZIndex = -1;
this.opacity = 0;
}
}
},
computed: {
showBackTop() {
// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
return this.scrollTop > uni.upx2px(this.top);
},
},
data() {
return {
// 不透明度,为了让组件有一个显示和隐藏的过渡动画
opacity: 0,
// 组件的z-index值,隐藏时设置为-1,就会看不到
uZIndex: -1
}
},
methods: {
backToTop() {
uni.pageScrollTo({
scrollTop: 0,
duration: this.duration
});
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-back-top {
width: 80rpx;
height: 80rpx;
position: fixed;
z-index: 9;
@include vue-flex;
flex-direction: column;
justify-content: center;
background-color: #E1E1E1;
color: $u-content-color;
align-items: center;
transition: opacity 0.4s;
&__content {
@include vue-flex;
flex-direction: column;
align-items: center;
&__tips {
font-size: 24rpx;
transform: scale(0.8);
line-height: 1;
}
}
}
</style>
<template>
<view v-if="show" class="u-badge" :class="[
isDot ? 'u-badge-dot' : '',
size == 'mini' ? 'u-badge-mini' : '',
type ? 'u-badge--bg--' + type : ''
]" :style="[{
top: offset[0] + 'rpx',
right: offset[1] + 'rpx',
fontSize: fontSize + 'rpx',
position: absolute ? 'absolute' : 'static',
color: color,
backgroundColor: bgColor
}, boxStyle]"
>
{{showText}}
</view>
</template>
<script>
/**
* badge 角标
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
* @tutorial https://www.uviewui.com/components/badge.html
* @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏
* @property {Boolean} is-dot 不展示数字,只有一个小点(默认false)
* @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true)
* @property {String Number} overflow-count 展示封顶的数字值(默认99)
* @property {String} type 使用预设的背景颜色(默认error)
* @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false)
* @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default)
* @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20])
* @property {String} color 字体颜色(默认#ffffff)
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
* @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false)
* @example <u-badge type="error" count="7"></u-badge>
*/
export default {
name: 'u-badge',
props: {
// primary,warning,success,error,info
type: {
type: String,
default: 'error'
},
// default, mini
size: {
type: String,
default: 'default'
},
//是否是圆点
isDot: {
type: Boolean,
default: false
},
// 显示的数值内容
count: {
type: [Number, String],
},
// 展示封顶的数字值
overflowCount: {
type: Number,
default: 99
},
// 当数值为 0 时,是否展示 Badge
showZero: {
type: Boolean,
default: false
},
// 位置偏移
offset: {
type: Array,
default: () => {
return [20, 20]
}
},
// 是否开启绝对定位,开启了offset才会起作用
absolute: {
type: Boolean,
default: true
},
// 字体大小
fontSize: {
type: [String, Number],
default: '24'
},
// 字体演示
color: {
type: String,
default: '#ffffff'
},
// badge的背景颜色
bgColor: {
type: String,
default: ''
},
// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效
isCenter: {
type: Boolean,
default: false
}
},
computed: {
// 是否将badge中心与父组件右上角重合
boxStyle() {
let style = {};
if(this.isCenter) {
style.top = 0;
style.right = 0;
// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半
style.transform = "translateY(-50%) translateX(50%)";
} else {
style.top = this.offset[0] + 'rpx';
style.right = this.offset[1] + 'rpx';
style.transform = "translateY(0) translateX(0)";
}
// 如果尺寸为mini,后接上scal()
if(this.size == 'mini') {
style.transform = style.transform + " scale(0.8)";
}
return style;
},
// isDot类型时,不显示文字
showText() {
if(this.isDot) return '';
else {
if(this.count > this.overflowCount) return `${this.overflowCount}+`;
else return this.count;
}
},
// 是否显示组件
show() {
// 如果count的值为0,并且showZero设置为false,不显示组件
if(this.count == 0 && this.showZero == false) return false;
else return true;
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-badge {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
justify-content: center;
align-items: center;
line-height: 24rpx;
padding: 4rpx 8rpx;
border-radius: 100rpx;
z-index: 9;
&--bg--primary {
background-color: $u-type-primary;
}
&--bg--error {
background-color: $u-type-error;
}
&--bg--success {
background-color: $u-type-success;
}
&--bg--info {
background-color: $u-type-info;
}
&--bg--warning {
background-color: $u-type-warning;
}
}
.u-badge-dot {
height: 16rpx;
width: 16rpx;
border-radius: 100rpx;
line-height: 1;
}
.u-badge-mini {
transform: scale(0.8);
transform-origin: center center;
}
// .u-primary {
// background: $u-type-primary;
// color: #fff;
// }
// .u-error {
// background: $u-type-error;
// color: #fff;
// }
// .u-warning {
// background: $u-type-warning;
// color: #fff;
// }
// .u-success {
// background: $u-type-success;
// color: #fff;
// }
// .u-black {
// background: #585858;
// color: #fff;
// }
.u-info {
background-color: $u-type-info;
color: #fff;
}
</style>
\ No newline at end of file
<template>
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
<view class="u-keyboard-grids">
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
<view :hover-stay-time="100" @touchstart="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
v-for="(item, j) in group" :key="j">
{{ item }}
</view>
</view>
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
hover-class="u-hover-class">
<u-icon :size="38" name="backspace" :bold="true"></u-icon>
</view>
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @click="changeCarInputMode">
<text class="zh" :class="[!abc ? 'active' : 'inactive']"></text>
/
<text class="en" :class="[abc ? 'active' : 'inactive']"></text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "u-keyboard",
emits: ["change", "backspace"],
props: {
// 是否打乱键盘按键的顺序
random: {
type: Boolean,
default: false
}
},
data() {
return {
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
abc: false
};
},
computed: {
areaList() {
let data = [
'京',
'沪',
'粤',
'津',
'冀',
'豫',
'云',
'辽',
'黑',
'湘',
'皖',
'鲁',
'苏',
'浙',
'赣',
'鄂',
'桂',
'甘',
'晋',
'陕',
'蒙',
'吉',
'闽',
'贵',
'渝',
'川',
'青',
'琼',
'宁',
'挂',
'藏',
'港',
'澳',
'新',
'使',
'学'
];
let tmp = [];
// 打乱顺序
if (this.random) data = this.$u.randomArray(data);
// 切割成二维数组
tmp[0] = data.slice(0, 10);
tmp[1] = data.slice(10, 20);
tmp[2] = data.slice(20, 30);
tmp[3] = data.slice(30, 36);
return tmp;
},
EngKeyBoardList() {
let data = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
0,
'Q',
'W',
'E',
'R',
'T',
'Y',
'U',
'I',
'O',
'P',
'A',
'S',
'D',
'F',
'G',
'H',
'J',
'K',
'L',
'Z',
'X',
'C',
'V',
'B',
'N',
'M'
];
let tmp = [];
if (this.random) data = this.$u.randomArray(data);
tmp[0] = data.slice(0, 10);
tmp[1] = data.slice(10, 20);
tmp[2] = data.slice(20, 30);
tmp[3] = data.slice(30, 36);
return tmp;
}
},
methods: {
// 点击键盘按钮
carInputClick(i, j) {
let value = '';
// 不同模式,获取不同数组的值
if (this.abc) value = this.EngKeyBoardList[i][j];
else value = this.areaList[i][j];
if(!this.abc) this.abc = true;
this.$emit('change', value);
if(this.vibrate) uni.vibrateShort();
},
// 修改汽车牌键盘的输入模式,中文|英文
changeCarInputMode() {
this.abc = !this.abc;
},
// 修改汽车牌键盘的输入模式,中文|英文
updateCarInputMode(abc) {
this.abc = abc;
},
// 点击退格键
backspaceClick() {
let count = 1;
this.backspaceFn(count);
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
this.timer = null;
this.timer = setInterval(() => {
count++;
this.backspaceFn(count);
}, 250);
},
backspaceFn(count){
this.$emit('backspace',count);
},
clearTimer() {
clearInterval(this.timer);
this.timer = null;
},
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-keyboard-grids {
background: rgb(215, 215, 217);
padding: 24rpx 0;
position: relative;
}
.u-keyboard-grids-item {
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-keyboard-grids-btn {
text-decoration: none;
width: 62rpx;
flex: 0 0 64rpx;
height: 80rpx;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
font-size: 36rpx;
text-align: center;
line-height: 80rpx;
background-color: #fff;
margin: 8rpx 5rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx #888992;
font-weight: 500;
justify-content: center;
}
.u-carinput-hover {
background-color: rgb(185, 188, 195) !important;
}
.u-keyboard-back {
position: absolute;
width: 96rpx;
right: 22rpx;
bottom: 32rpx;
height: 80rpx;
background-color: rgb(185, 188, 195);
@include vue-flex;
align-items: center;
border-radius: 8rpx;
justify-content: center;
box-shadow: 0 2rpx 0rpx #888992;
}
.u-keyboard-change {
font-size: 24rpx;
box-shadow: 0 2rpx 0rpx #888992;
position: absolute;
width: 96rpx;
left: 22rpx;
line-height: 1;
bottom: 32rpx;
height: 80rpx;
background-color: #ffffff;
@include vue-flex;
align-items: center;
border-radius: 8rpx;
justify-content: center;
}
.u-keyboard-change .inactive.zh {
transform: scale(0.85) translateY(-10rpx);
}
.u-keyboard-change .inactive.en {
transform: scale(0.85) translateY(10rpx);
}
.u-keyboard-change .active {
color: rgb(237, 112, 64);
font-size: 30rpx;
}
.u-keyboard-change .zh {
transform: translateY(-10rpx);
}
.u-keyboard-change .en {
transform: translateY(10rpx);
}
</style>
<template>
<view
class="u-card"
@tap.stop="click"
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }"
:style="{
borderRadius: borderRadius + 'rpx',
margin: margin,
boxShadow: boxShadow
}"
>
<view
v-if="showHead"
class="u-card__head"
:style="[{padding: padding + 'rpx'}, headStyle]"
:class="{
'u-border-bottom': headBorderBottom
}"
@tap="headClick"
>
<view v-if="!$slots.head" class="u-flex u-row-between">
<view class="u-card__head--left u-flex u-line-1" v-if="title">
<image
:src="thumb"
class="u-card__head--left__thumb"
mode="aspectfull"
v-if="thumb"
:style="{
height: thumbWidth + 'rpx',
width: thumbWidth + 'rpx',
borderRadius: thumbCircle ? '100rpx' : '6rpx'
}"
></image>
<text
class="u-card__head--left__title u-line-1"
:style="{
fontSize: titleSize + 'rpx',
color: titleColor
}"
>
{{ title }}
</text>
</view>
<view class="u-card__head--right u-line-1" v-if="subTitle">
<text
class="u-card__head__title__text"
:style="{
fontSize: subTitleSize + 'rpx',
color: subTitleColor
}"
>
{{ subTitle }}
</text>
</view>
</view>
<slot name="head" v-else />
</view>
<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view>
<view
v-if="showFoot"
class="u-card__foot"
@tap="footClick"
:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]"
:class="{
'u-border-top': footBorderTop
}"
>
<slot name="foot" />
</view>
</view>
</template>
<script>
/**
* card 卡片
* @description 卡片组件一般用于多个列表条目,且风格统一的场景
* @tutorial https://www.uviewui.com/components/card.html
* @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
* @property {String} title 头部左边的标题
* @property {String} title-color 标题颜色(默认#303133)
* @property {String | Number} title-size 标题字体大小,单位rpx(默认30)
* @property {String} sub-title 头部右边的副标题
* @property {String} sub-title-color 副标题颜色(默认#909399)
* @property {String | Number} sub-title-size 副标题字体大小(默认26)
* @property {Boolean} border 是否显示边框(默认true)
* @property {String | Number} index 用于标识点击了第几个卡片
* @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
* @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx)
* @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16)
* @property {Object} head-style 头部自定义样式,对象形式
* @property {Object} body-style 中部自定义样式,对象形式
* @property {Object} foot-style 底部自定义样式,对象形式
* @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
* @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
* @property {Boolean} show-head 是否显示头部(默认true)
* @property {Boolean} show-head 是否显示尾部(默认true)
* @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
* @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60)
* @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
* @event {Function} click 整个卡片任意位置被点击时触发
* @event {Function} head-click 卡片头部被点击时触发
* @event {Function} body-click 卡片主体部分被点击时触发
* @event {Function} foot-click 卡片底部部分被点击时触发
* @example <u-card padding="30" title="card"></u-card>
*/
export default {
name: 'u-card',
emits: ["click", "head-click", "body-click", "foot-click"],
props: {
// 与屏幕两侧是否留空隙
full: {
type: Boolean,
default: false
},
// 标题
title: {
type: String,
default: ''
},
// 标题颜色
titleColor: {
type: String,
default: '#303133'
},
// 标题字体大小,单位rpx
titleSize: {
type: [Number, String],
default: '30'
},
// 副标题
subTitle: {
type: String,
default: ''
},
// 副标题颜色
subTitleColor: {
type: String,
default: '#909399'
},
// 副标题字体大小,单位rpx
subTitleSize: {
type: [Number, String],
default: '26'
},
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
border: {
type: Boolean,
default: true
},
// 用于标识点击了第几个
index: {
type: [Number, String, Object],
default: ''
},
// 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx"
margin: {
type: String,
default: '30rpx'
},
// card卡片的圆角
borderRadius: {
type: [Number, String],
default: '16'
},
// 头部自定义样式,对象形式
headStyle: {
type: Object,
default() {
return {};
}
},
// 主体自定义样式,对象形式
bodyStyle: {
type: Object,
default() {
return {};
}
},
// 底部自定义样式,对象形式
footStyle: {
type: Object,
default() {
return {};
}
},
// 头部是否下边框
headBorderBottom: {
type: Boolean,
default: true
},
// 底部是否有上边框
footBorderTop: {
type: Boolean,
default: true
},
// 标题左边的缩略图
thumb: {
type: String,
default: ''
},
// 缩略图宽高,单位rpx
thumbWidth: {
type: [String, Number],
default: '60'
},
// 缩略图是否为圆形
thumbCircle: {
type: Boolean,
default: false
},
// 给head,body,foot的内边距
padding: {
type: [String, Number],
default: '30'
},
// 是否显示头部
showHead: {
type: Boolean,
default: true
},
// 是否显示尾部
showFoot: {
type: Boolean,
default: true
},
// 卡片外围阴影,字符串形式
boxShadow: {
type: String,
default: 'none'
}
},
data() {
return {};
},
methods: {
click() {
this.$emit('click', this.index);
},
headClick() {
this.$emit('head-click', this.index);
},
bodyClick() {
this.$emit('body-click', this.index);
},
footClick() {
this.$emit('foot-click', this.index);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-card {
position: relative;
overflow: hidden;
font-size: 28rpx;
background-color: #ffffff;
box-sizing: border-box;
&-full {
// 如果是与屏幕之间不留空隙,应该设置左右边距为0
margin-left: 0 !important;
margin-right: 0 !important;
width: 100%;
}
&--border:after {
border-radius: 16rpx;
}
&__head {
&--left {
color: $u-main-color;
&__thumb {
margin-right: 16rpx;
}
&__title {
max-width: 400rpx;
}
}
&--right {
color: $u-tips-color;
margin-left: 6rpx;
}
}
&__body {
color: $u-content-color;
}
&__foot {
color: $u-tips-color;
}
}
</style>
<template>
<view class="u-cell-box">
<view class="u-cell-title" v-if="title" :style="[titleStyle]">
{{title}}
</view>
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
<slot />
</view>
</view>
</template>
<script>
/**
* cellGroup 单元格父组件Group
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item
* @tutorial https://www.uviewui.com/components/cell.html
* @property {String} title 分组标题
* @property {Boolean} border 是否显示外边框(默认true)
* @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'}
* @example <u-cell-group title="设置喜好">
*/
export default {
name: "u-cell-group",
props: {
// 分组标题
title: {
type: String,
default: ''
},
// 是否显示分组list上下边框
border: {
type: Boolean,
default: true
},
// 分组标题的样式,对象形式,注意驼峰属性写法
// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'}
titleStyle: {
type: Object,
default () {
return {};
}
}
},
data() {
return {
index: 0,
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-cell-box {
width: 100%;
}
.u-cell-title {
padding: 30rpx 32rpx 10rpx 32rpx;
font-size: 30rpx;
text-align: left;
color: $u-tips-color;
}
.u-cell-item-box {
background-color: #FFFFFF;
flex-direction: row;
}
</style>
<template>
<view
@tap="click"
class="u-cell"
:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }"
hover-stay-time="150"
:hover-class="hoverClass"
:style="{
backgroundColor: bgColor
}"
>
<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon>
<view class="u-flex" v-else>
<slot name="icon"></slot>
</view>
<view
class="u-cell_title"
:style="[
{
width: titleWidth ? titleWidth + 'rpx' : 'auto'
},
titleStyle
]"
>
<block v-if="title !== ''">{{ title }}</block>
<slot name="title" v-else></slot>
<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]">
<block v-if="label !== ''">{{ label }}</block>
<slot name="label" v-else></slot>
</view>
</view>
<view class="u-cell__value" :style="[valueStyle]">
<block class="u-cell__value" v-if="value !== ''">{{ value }}</block>
<slot v-else></slot>
</view>
<view class="u-flex u-cell_right" v-if="$slots['right-icon']">
<slot name="right-icon"></slot>
</view>
<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon>
</view>
</template>
<script>
/**
* cellItem 单元格Item
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用
* @tutorial https://www.uviewui.com/components/cell.html
* @property {String} title 左侧标题
* @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标
* @property {Object} icon-style 左边图标的样式,对象形式
* @property {String} value 右侧内容
* @property {String} label 标题下方的描述信息
* @property {Boolean} border-bottom 是否显示cell的下边框(默认true)
* @property {Boolean} border-top 是否显示cell的上边框(默认false)
* @property {Boolean} center 是否使内容垂直居中(默认false)
* @property {String} hover-class 是否开启点击反馈,none为无效果(默认true)
* // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true)
* @property {Boolean} arrow 是否显示右侧箭头(默认true)
* @property {Boolean} required 箭头方向,可选值(默认right)
* @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false)
* @property {Object} title-style 标题样式,对象形式
* @property {Object} value-style 右侧内容样式,对象形式
* @property {Object} label-style 标题下方描述信息的样式,对象形式
* @property {String} bg-color 背景颜色(默认transparent)
* @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item
* @property {String Number} title-width 标题的宽度,单位rpx
* @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
*/
export default {
name: 'u-cell-item',
emits: ["click"],
props: {
// 左侧图标名称(只能uView内置图标),或者图标src
icon: {
type: String,
default: ''
},
// 左侧标题
title: {
type: [String, Number],
default: ''
},
// 右侧内容
value: {
type: [String, Number],
default: ''
},
// 标题下方的描述信息
label: {
type: [String, Number],
default: ''
},
// 是否显示下边框
borderBottom: {
type: Boolean,
default: true
},
// 是否显示上边框
borderTop: {
type: Boolean,
default: false
},
// 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离
// 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰
// borderGap: {
// type: Boolean,
// default: true
// },
// 是否开启点击反馈,即点击时cell背景为灰色,none为无效果
hoverClass: {
type: String,
default: 'u-cell-hover'
},
// 是否显示右侧箭头
arrow: {
type: Boolean,
default: true
},
// 内容是否垂直居中
center: {
type: Boolean,
default: false
},
// 是否显示左边表示必填的星号
required: {
type: Boolean,
default: false
},
// 标题的宽度,单位rpx
titleWidth: {
type: [Number, String],
default: ''
},
// 右侧箭头方向,可选值:right|up|down,默认为right
arrowDirection: {
type: String,
default: 'right'
},
// 控制标题的样式
titleStyle: {
type: Object,
default() {
return {};
}
},
// 右侧显示内容的样式
valueStyle: {
type: Object,
default() {
return {};
}
},
// 描述信息的样式
labelStyle: {
type: Object,
default() {
return {};
}
},
// 背景颜色
bgColor: {
type: String,
default: 'transparent'
},
// 用于识别被点击的是第几个cell
index: {
type: [String, Number],
default: ''
},
// 是否使用lable插槽
useLabelSlot: {
type: Boolean,
default: false
},
// 左边图标的大小,单位rpx,只对传入icon字段时有效
iconSize: {
type: [Number, String],
default: 34
},
// 左边图标的样式,对象形式
iconStyle: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
};
},
computed: {
arrowStyle() {
let style = {};
if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)';
else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)';
else style.transform = 'rotate(0deg)';
return style;
}
},
methods: {
click() {
this.$emit('click', this.index);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-cell {
@include vue-flex;
align-items: center;
position: relative;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
width: 100%;
padding: 26rpx 32rpx;
font-size: 28rpx;
line-height: 54rpx;
color: $u-content-color;
background-color: #fff;
text-align: left;
}
.u-cell_title {
font-size: 28rpx;
}
.u-cell__left-icon-wrap {
margin-right: 10rpx;
font-size: 32rpx;
}
.u-cell__right-icon-wrap {
margin-left: 10rpx;
color: #969799;
font-size: 28rpx;
}
.u-cell__left-icon-wrap,
.u-cell__right-icon-wrap {
@include vue-flex;
align-items: center;
height: 48rpx;
}
.u-cell-border:after {
position: absolute;
/* #ifndef APP-NVUE */
box-sizing: border-box;
content: ' ';
pointer-events: none;
border-bottom: 1px solid $u-border-color;
/* #endif */
right: 0;
left: 0;
top: 0;
transform: scaleY(0.5);
}
.u-cell-border {
position: relative;
}
.u-cell__label {
margin-top: 6rpx;
font-size: 26rpx;
line-height: 36rpx;
color: $u-tips-color;
/* #ifndef APP-NVUE */
word-wrap: break-word;
/* #endif */
}
.u-cell__value {
overflow: hidden;
text-align: right;
/* #ifndef APP-NVUE */
vertical-align: middle;
/* #endif */
color: $u-tips-color;
font-size: 26rpx;
}
.u-cell__title,
.u-cell__value {
flex: 1;
}
.u-cell--required {
/* #ifndef APP-NVUE */
overflow: visible;
/* #endif */
@include vue-flex;
align-items: center;
}
.u-cell--required:before {
position: absolute;
/* #ifndef APP-NVUE */
content: '*';
/* #endif */
left: 8px;
margin-top: 4rpx;
font-size: 14px;
color: $u-type-error;
}
.u-cell_right {
line-height: 1;
}
</style>
<template>
<view class="u-checkbox-group u-clearfix" :class="uFromData.inputAlign == 'right' ? 'flex-end' : ''"><slot></slot></view>
</template>
<script>
import Emitter from "../../libs/util/emitter.js";
/**
* checkboxGroup 开关选择器父组件Group
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
* @tutorial https://www.uviewui.com/components/checkbox.html
* @property {String Number} max 最多能选中多少个checkbox(默认999)
* @property {String Number} size 组件整体的大小,单位rpx(默认40)
* @property {Boolean} disabled 是否禁用所有checkbox(默认false)
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
* @property {String} width 宽度,需带单位
* @property {String} width 宽度,需带单位
* @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle)
* @property {Boolean} wrap 是否每个checkbox都换行(默认false)
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff)
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
* @example <u-checkbox-group></u-checkbox-group>
*/
export default {
name: "u-checkbox-group",
emits: ["update:modelValue", "input", "change"],
mixins: [Emitter],
props: {
// 匹配某一个radio组件,如果某个radio的name值等于此值,那么这个radio就被会选中
value: {
type: [String, Number, Array, Boolean],
default: ""
},
modelValue: {
type: [String, Number, Array, Boolean],
default: ""
},
// 最多能选中多少个checkbox
max: {
type: [Number, String],
default: 999
},
// 所有选中项的 name
// value: {
// default: Array,
// default() {
// return []
// }
// },
// 是否禁用所有复选框
disabled: {
type: Boolean,
default: false
},
// 在表单内提交时的标识符
name: {
type: [Boolean, String],
default: ""
},
// 是否禁止点击提示语选中复选框
labelDisabled: {
type: Boolean,
default: false
},
// 形状,square为方形,circle为圆型
shape: {
type: String,
default: "square"
},
// 选中状态下的颜色
activeColor: {
type: String,
default: "#2979ff"
},
// 组件的整体大小
size: {
type: [String, Number],
default: 34
},
// 每个checkbox占u-checkbox-group的宽度
width: {
type: String,
default: "auto"
},
// 是否每个checkbox都换行
wrap: {
type: Boolean,
default: false
},
// 图标的大小,单位rpx
iconSize: {
type: [String, Number],
default: 20
}
},
data() {
return {
values: [],
uFromData: {
inputAlign: "left"
}
};
},
created() {
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
this.children = [];
},
mounted() {
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
let parent = this.$u.$parent.call(this, "u-form");
if (parent) {
Object.keys(this.uFromData).map(key => {
this.uFromData[key] = parent[key];
});
}
},
methods: {
emitEvent(obj) {
let values = this.values || [];
if (obj.value) {
let index = values.indexOf(obj.name);
if (index === -1) {
values.push(obj.name);
}
} else {
let index = values.indexOf(obj.name);
if (index > -1) {
values.splice(index, 1);
}
}
this.$emit("change", values);
// 通过emit事件,设置父组件通过v-model双向绑定的值
this.$emit("input", values);
this.$emit("update:modelValue", values);
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
setTimeout(() => {
// 将当前的值发送到 u-form-item 进行校验
this.dispatch("u-form-item", "onFieldChange", values);
}, 60);
},
_emitEvent(obj) {
let values = this.values || [];
if (obj.value) {
let index = values.indexOf(obj.name);
if (index === -1) {
values.push(obj.name);
}
} else {
let index = values.indexOf(obj.name);
if (index > -1) {
values.splice(index, 1);
}
}
//this.$emit("change", values);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-checkbox-group {
/* #ifndef MP || APP-NVUE */
display: inline-flex;
flex-wrap: wrap;
/* #endif */
}
.u-checkbox-group.flex-end {
/* #ifndef APP-NVUE */
display: inline-flex;
justify-content: flex-end;
flex-wrap: wrap;
/* #endif */
}
</style>
<template>
<view class="u-checkbox" :style="[checkboxStyle]">
<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
<u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor" />
</view>
<view
class="u-checkbox__label"
@tap="onClickLabel"
:style="{
fontSize: $u.addUnit(labelSize)
}"
>
<slot />
</view>
</view>
</template>
<script>
/**
* checkbox 复选框
* @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。
* @tutorial https://www.uviewui.com/components/checkbox.html
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
* @property {String Number} label-size label字体大小,单位rpx(默认28)
* @property {String Number} name checkbox组件的标示符
* @property {String} shape 形状,外观形状,shape-方形,circle-圆形(默认circle)
* @property {Boolean} disabled 是否禁用
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox
* @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效
* @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
*/
export default {
name: "u-checkbox",
emits: ["update:modelValue", "input", "change"],
props: {
// 是否为选中状态
value: {
type: Boolean,
default: false
},
modelValue: {
type: Boolean,
default: false
},
// checkbox的名称
name: {
type: [String, Number],
default: ""
},
// 形状,square为方形,circle为圆型
shape: {
type: String,
default: ""
},
// 是否禁用
disabled: {
type: [String, Boolean],
default: ""
},
// 是否禁止点击提示语选中复选框
labelDisabled: {
type: [String, Boolean],
default: ""
},
// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值
activeColor: {
type: String,
default: ""
},
// 图标的大小,单位rpx
iconSize: {
type: [String, Number],
default: ""
},
// label的字体大小,rpx单位
labelSize: {
type: [String, Number],
default: ""
},
// 组件的整体大小
size: {
type: [String, Number],
default: ""
}
},
data() {
return {
parentDisabled: false,
newParams: {}
};
},
created() {
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
this.parent = this.$u.$parent.call(this, "u-checkbox-group");
// 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
this.parent && this.parent.children.push(this);
},
computed: {
valueCom() {
// #ifndef VUE3
return this.value;
// #endif
// #ifdef VUE3
return this.modelValue;
// #endif
},
// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置
isDisabled() {
return this.disabled !== "" ? this.disabled : this.parent ? this.parent.disabled : false;
},
// 是否禁用label点击
isLabelDisabled() {
return this.labelDisabled !== "" ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
},
// 组件尺寸,对应size的值,默认值为34rpx
checkboxSize() {
return this.size ? this.size : this.parent ? this.parent.size : 34;
},
// 组件的勾选图标的尺寸,默认20
checkboxIconSize() {
return this.iconSize ? this.iconSize : this.parent ? this.parent.iconSize : 20;
},
// 组件选中激活时的颜色
elActiveColor() {
return this.activeColor ? this.activeColor : this.parent ? this.parent.activeColor : "primary";
},
// 组件的形状
elShape() {
return this.shape ? this.shape : this.parent ? this.parent.shape : "square";
},
iconStyle() {
let style = {};
// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中
if (this.elActiveColor && this.valueCom && !this.isDisabled) {
style.borderColor = this.elActiveColor;
style.backgroundColor = this.elActiveColor;
}
style.width = this.$u.addUnit(this.checkboxSize);
style.height = this.$u.addUnit(this.checkboxSize);
return style;
},
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
iconColor() {
return this.valueCom ? "#ffffff" : "transparent";
},
iconClass() {
let classes = [];
classes.push("u-checkbox__icon-wrap--" + this.elShape);
if (this.valueCom == true) classes.push("u-checkbox__icon-wrap--checked");
if (this.isDisabled) classes.push("u-checkbox__icon-wrap--disabled");
if (this.valueCom && this.isDisabled) classes.push("u-checkbox__icon-wrap--disabled--checked");
// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
return classes.join(" ");
},
checkboxStyle() {
let style = {};
if (this.parent && this.parent.width) {
style.width = this.parent.width;
// #ifdef MP
// 各家小程序因为它们特殊的编译结构,使用float布局
style.float = "left";
// #endif
// #ifndef MP
// H5和APP使用flex布局
style.flex = `0 0 ${this.parent.width}`;
// #endif
}
if (this.parent && this.parent.wrap) {
style.width = "100%";
// #ifndef MP
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
style.flex = "0 0 100%";
// #endif
}
return style;
}
},
mounted() {
this._emitEvent();
},
watch: {
valueCom: {
handler: function(newVal, oldVal) {
this._emitEvent();
}
}
},
methods: {
_emitEvent() {
let value = this.valueCom;
let obj = {
value,
name: this.name
};
// 执行父组件u-checkbox-group的事件方法
if (this.parent && this.parent.emitEvent) this.parent._emitEvent(obj);
},
onClickLabel() {
if (!this.isLabelDisabled && !this.isDisabled) {
this.setValue();
}
},
toggle() {
if (!this.isDisabled) {
this.setValue();
}
},
emitEvent() {
let obj = {
value: !this.valueCom,
name: this.name
};
this.$emit("change", obj);
// 执行父组件u-checkbox-group的事件方法
if (this.parent && this.parent.emitEvent) this.parent.emitEvent(obj);
},
// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值
setValue() {
let value = this.valueCom;
// 判断是否超过了可选的最大数量
let checkedNum = 0;
if (this.parent && this.parent.children) {
// 只要父组件的某一个子元素的value为true,就加1(已有的选中数量)
this.parent.children.map(val => {
if (val.value) checkedNum++;
});
}
// 如果原来为选中状态,那么可以取消
if (value == true) {
this.emitEvent();
this.$emit("input", !value);
this.$emit("update:modelValue", !value);
} else {
// 如果超出最多可选项,提示
if (this.parent && checkedNum >= this.parent.max) {
return this.$u.toast(`最多可选${this.parent.max}项`);
}
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
this.emitEvent();
this.$emit("input", !value);
this.$emit("update:modelValue", !value);
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-checkbox {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
overflow: hidden;
user-select: none;
line-height: 1.8;
&__icon-wrap {
color: $u-content-color;
flex: none;
display: -webkit-flex;
@include vue-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 42rpx;
height: 42rpx;
color: transparent;
text-align: center;
transition-property: color, border-color, background-color;
font-size: 20px;
border: 1px solid #c8c9cc;
transition-duration: 0.2s;
/* #ifdef MP-TOUTIAO */
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
&__icon {
line-height: 0;
}
/* #endif */
&--circle {
border-radius: 100%;
}
&--square {
border-radius: 6rpx;
}
&--checked {
color: #fff;
background-color: $u-type-primary;
border-color: $u-type-primary;
}
&--disabled {
background-color: #ebedf0;
border-color: #c8c9cc;
}
&--disabled--checked {
color: #c8c9cc !important;
}
}
&__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 24rpx;
color: $u-content-color;
font-size: 30rpx;
&--disabled {
color: #c8c9cc;
}
}
}
</style>
<template>
<view
class="u-circle-progress"
:style="{
width: widthPx + 'px',
height: widthPx + 'px',
backgroundColor: bgColor
}"
>
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
<canvas
class="u-canvas-bg"
:canvas-id="elBgId"
:id="elBgId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<canvas
class="u-canvas"
:canvas-id="elId"
:id="elId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<slot></slot>
</view>
</template>
<script>
/**
* circleProgress 环形进度条
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。
* @tutorial https://www.uviewui.com/components/circleProgress.html
* @property {String Number} percent 圆环进度百分比值,为数值类型,0-100
* @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec)
* @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b)
* @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200)
* @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14)
* @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500)
* @property {String} type 如设置,active-color值将会失效
* @property {String} bg-color 整个组件背景颜色,默认为白色
* @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
*/
export default {
name: 'u-circle-progress',
props: {
// 圆环进度百分比值
percent: {
type: Number,
default: 0,
// 限制值在0到100之间
validator: val => {
return val >= 0 && val <= 100;
}
},
// 底部圆环的颜色(灰色的圆环)
inactiveColor: {
type: String,
default: '#ececec'
},
// 圆环激活部分的颜色
activeColor: {
type: String,
default: '#19be6b'
},
// 圆环线条的宽度,单位rpx
borderWidth: {
type: [Number, String],
default: 14
},
// 整个圆形的宽度,单位rpx
width: {
type: [Number, String],
default: 200
},
// 整个圆环执行一圈的时间,单位ms
duration: {
type: [Number, String],
default: 1500
},
// 主题类型
type: {
type: String,
default: ''
},
// 整个圆环进度区域的背景色
bgColor: {
type: String,
default: '#ffffff'
}
},
data() {
return {
// #ifdef MP-WEIXIN
elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错
elId: 'uCircleProgressElId',
// #endif
// #ifndef MP-WEIXIN
elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱
elId: this.$u.guid(),
// #endif
widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度
borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度
startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向
progressContext: null, // 活动圆的canvas上下文
newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
};
},
watch: {
percent(nVal, oVal = 0) {
if (nVal > 100) nVal = 100;
if (nVal < 0) oVal = 0;
// 此值其实等于this.percent,命名一个新
this.newPercent = nVal;
this.oldPercent = oVal;
setTimeout(() => {
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
// 将此值减少或者新增到新的百分比值
this.drawCircleByProgress(oVal);
}, 50);
}
},
created() {
// 赋值,用于加载后第一个画圆使用
this.newPercent = this.percent;
this.oldPercent = 0;
},
computed: {
// 有type主题时,优先起作用
circleColor() {
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
else return this.activeColor;
}
},
mounted() {
// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7)
setTimeout(() => {
this.drawProgressBg();
this.drawCircleByProgress(this.oldPercent);
}, 50);
},
methods: {
drawProgressBg() {
let ctx = uni.createCanvasContext(this.elBgId, this);
ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度
ctx.setStrokeStyle(this.inactiveColor); // 线条颜色
ctx.beginPath(); // 开始描绘路径
// 设置一个原点(110,110),半径为100的圆的路径到当前路径
let radius = this.widthPx / 2;
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false);
ctx.stroke(); // 对路径进行描绘
ctx.draw();
},
drawCircleByProgress(progress) {
// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可
let ctx = this.progressContext;
if (!ctx) {
ctx = uni.createCanvasContext(this.elId, this);
this.progressContext = ctx;
}
// 表示进度的两端为圆形
ctx.setLineCap('round');
// 设置线条的宽度和颜色
ctx.setLineWidth(this.borderWidthPx);
ctx.setStrokeStyle(this.circleColor);
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
let time = Math.floor(this.duration / 100);
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
// 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值
let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle;
ctx.beginPath();
// 半径为整个canvas宽度的一半
let radius = this.widthPx / 2;
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false);
ctx.stroke();
ctx.draw();
// 如果变更后新值大于旧值,意味着增大了百分比
if (this.newPercent > this.oldPercent) {
// 每次递增百分之一
progress++;
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
if (progress > this.newPercent) return;
} else {
// 同理于上面
progress--;
if (progress < this.newPercent) return;
}
setTimeout(() => {
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
this.drawCircleByProgress(progress);
}, time);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-circle-progress {
position: relative;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
justify-content: center;
}
.u-canvas-bg {
position: absolute;
}
.u-canvas {
position: absolute;
}
</style>
<template>
<view class="u-col" :class="[
'u-col-' + span
]" :style="{
padding: `0 ${Number(gutter)/2 + 'rpx'}`,
marginLeft: 100 / 12 * offset + '%',
flex: `0 0 ${100 / 12 * span}%`,
alignItems: uAlignItem,
justifyContent: uJustify,
textAlign: textAlign
}"
@tap="click">
<slot></slot>
</view>
</template>
<script>
/**
* col 布局单元格
* @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用)
* @tutorial https://www.uviewui.com/components/layout.html
* @property {String Number} span 栅格占据的列数,总12等分(默认0)
* @property {String} text-align 文字水平对齐方式(默认left)
* @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0)
* @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col>
*/
export default {
name: "u-col",
props: {
// 占父容器宽度的多少等分,总分为12份
span: {
type: [Number, String],
default: 12
},
// 指定栅格左侧的间隔数(总12栏)
offset: {
type: [Number, String],
default: 0
},
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
justify: {
type: String,
default: 'start'
},
// 垂直对齐方式,可选值为top、center、bottom
align: {
type: String,
default: 'center'
},
// 文字对齐方式
textAlign: {
type: String,
default: 'left'
},
// 是否阻止事件传播
stop: {
type: Boolean,
default: true
}
},
data() {
return {
gutter: 20, // 给col添加间距,左右边距各占一半,从父组件u-row获取
}
},
created() {
this.parent = false;
},
mounted() {
// 获取父组件实例,并赋值给对应的参数
this.parent = this.$u.$parent.call(this, 'u-row');
if (this.parent) {
this.gutter = this.parent.gutter;
}
},
computed: {
uJustify() {
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify;
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify;
else return this.justify;
},
uAlignItem() {
if (this.align == 'top') return 'flex-start';
if (this.align == 'bottom') return 'flex-end';
else return this.align;
}
},
methods: {
click(e) {
this.$emit('click');
}
}
}
</script>
<style lang="scss">
@import "../../libs/css/style.components.scss";
.u-col {
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
float: left;
/* #endif */
}
.u-col-0 {
width: 0;
}
.u-col-1 {
width: calc(100%/12);
}
.u-col-2 {
width: calc(100%/12 * 2);
}
.u-col-3 {
width: calc(100%/12 * 3);
}
.u-col-4 {
width: calc(100%/12 * 4);
}
.u-col-5 {
width: calc(100%/12 * 5);
}
.u-col-6 {
width: calc(100%/12 * 6);
}
.u-col-7 {
width: calc(100%/12 * 7);
}
.u-col-8 {
width: calc(100%/12 * 8);
}
.u-col-9 {
width: calc(100%/12 * 9);
}
.u-col-10 {
width: calc(100%/12 * 10);
}
.u-col-11 {
width: calc(100%/12 * 11);
}
.u-col-12 {
width: calc(100%/12 * 12);
}
</style>
<template>
<view class="u-collapse-item" :style="[itemStyle]">
<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]">
<block v-if="!$slots['title-all']">
<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
isShow && activeStyle && !arrow ? activeStyle : '']">
{{ title }}
</view>
<slot v-else name="title" />
<view class="u-icon-wrap">
<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }"
class="u-arrow-down-icon" name="arrow-down"></u-icon>
</view>
</block>
<slot v-else name="title-all" />
</view>
<view class="u-collapse-body" :style="[{
height: isShow ? height + 'px' : '0'
}]">
<view class="u-collapse-content" :id="elId" :style="[bodyStyle]">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* collapseItem 手风琴Item
* @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
* @tutorial https://www.uviewui.com/components/collapse.html
* @property {String} title 面板标题
* @property {String Number} index 主要用于事件的回调,标识那个Item被点击
* @property {Boolean} disabled 面板是否可以打开或收起(默认false)
* @property {Boolean} open 设置某个面板的初始状态是否打开(默认false)
* @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值
* @property {String} align 标题的对齐方式(默认left)
* @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式
* @event {Function} change 某个item被打开或者收起时触发
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
*/
export default {
name: "u-collapse-item",
emits: ["change"],
props: {
// 标题
title: {
type: String,
default: ''
},
// 标题的对齐方式
align: {
type: String,
default: 'left'
},
// 是否可以点击收起
disabled: {
type: Boolean,
default: false
},
// collapse显示与否
open: {
type: Boolean,
default: false
},
// 唯一标识符
name: {
type: [Number, String],
default: ''
},
//活动样式
activeStyle: {
type: Object,
default () {
return {}
}
},
// 标识当前为第几个
index: {
type: [String, Number],
default: ''
}
},
data() {
return {
isShow: false,
elId: this.$u.guid(),
height: 0, // body内容的高度
headStyle: {}, // 头部样式,对象形式
bodyStyle: {}, // 主体部分样式
itemStyle: {}, // 每个item的整体样式
arrowColor: '', // 箭头的颜色
hoverClass: '', // 头部按下时的效果样式类
arrow: true, // 是否显示右侧箭头
};
},
watch: {
open(val) {
this.isShow = val;
}
},
created() {
this.parent = false;
// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍
this.isShow = this.open;
},
methods: {
// 异步获取内容,或者动态修改了内容时,需要重新初始化
init() {
this.parent = this.$u.$parent.call(this, 'u-collapse');
if(this.parent) {
this.nameSync = this.name ? this.name : this.parent.childrens.length;
this.parent.childrens.push(this);
this.headStyle = this.parent.headStyle;
this.bodyStyle = this.parent.bodyStyle;
this.arrowColor = this.parent.arrowColor;
this.hoverClass = this.parent.hoverClass;
this.arrow = this.parent.arrow;
this.itemStyle = this.parent.itemStyle;
}
this.$nextTick(() => {
this.queryRect();
});
},
// 点击collapsehead头部
headClick() {
if (this.disabled) return;
if (this.parent && this.parent.accordion == true) {
this.parent.childrens.map(val => {
// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
if (this != val) {
val.isShow = false;
}
});
}
this.isShow = !this.isShow;
// 触发本组件的事件
this.$emit('change', {
index: this.index,
show: this.isShow
})
// 只有在打开时才发出事件
if (this.isShow) this.parent && this.parent.onChange();
this.$forceUpdate();
},
// 查询内容高度
queryRect() {
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
this.$uGetRect('#' + this.elId).then(res => {
this.height = res.height;
})
}
},
mounted() {
this.init();
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-collapse-head {
position: relative;
@include vue-flex;
justify-content: space-between;
align-items: center;
color: $u-main-color;
font-size: 30rpx;
line-height: 1;
padding: 24rpx 0;
text-align: left;
}
.u-collapse-title {
flex: 1;
overflow: hidden;
}
.u-arrow-down-icon {
transition: all 0.3s;
margin-right: 20rpx;
margin-left: 14rpx;
}
.u-arrow-down-icon-active {
transform: rotate(180deg);
transform-origin: center center;
}
.u-collapse-body {
overflow: hidden;
transition: all 0.3s;
}
.u-collapse-content {
overflow: hidden;
font-size: 28rpx;
color: $u-tips-color;
text-align: left;
}
</style>
差异被折叠。
差异被折叠。
差异被折叠。
This source diff could not be displayed because it is too large. You can view the blob instead.
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论