修改环境变量配置,app目录-->uniapp
22
uniapp/src/App.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from './stores/app'
|
||||
import { useUserStore } from './stores/user'
|
||||
const appStore = useAppStore()
|
||||
const { getUser } = useUserStore()
|
||||
|
||||
onLaunch(async () => {
|
||||
await appStore.getConfig()
|
||||
// #ifdef H5
|
||||
const { status, close, url } = appStore.getH5Config
|
||||
if (status == 0) {
|
||||
if (close == 1) return (location.href = url)
|
||||
uni.reLaunch({ url: '/pages/empty/empty' })
|
||||
}
|
||||
// #endif
|
||||
await getUser()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
//
|
||||
</style>
|
||||
26
uniapp/src/api/account.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { client } from '@/utils/client'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 登录
|
||||
export function login(data: Record<string, any>) {
|
||||
return request.post({ url: '/login/check', data: { ...data, client } })
|
||||
}
|
||||
|
||||
//注册
|
||||
export function register(data: Record<string, any>) {
|
||||
return request.post({ url: '/login/register', data: { ...data, client } })
|
||||
}
|
||||
|
||||
//忘记密码
|
||||
export function forgotPassword(data: Record<string, any>) {
|
||||
return request.post({ url: '/login/forgotPassword', data })
|
||||
}
|
||||
|
||||
//向微信请求code的链接
|
||||
export function getWxCodeUrl() {
|
||||
return request.get({ url: '/login/codeUrl', data: { url: location.href } })
|
||||
}
|
||||
|
||||
export function OALogin(data: Record<string, any>) {
|
||||
return request.get({ url: '/login/oaLogin', data })
|
||||
}
|
||||
14
uniapp/src/api/app.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
//发送短信
|
||||
export function smsSend(data: any) {
|
||||
return request.post({ url: '/sms/send', data: data })
|
||||
}
|
||||
|
||||
export function getConfig() {
|
||||
return request.get({ url: '/config' })
|
||||
}
|
||||
|
||||
export function getPolicy(data: any) {
|
||||
return request.get({ url: '/policy', data: data })
|
||||
}
|
||||
52
uniapp/src/api/news.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @description 获取文章分类
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getArticleCate() {
|
||||
return request.get({ url: '/article/category' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文章列表
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getArticleList(data: Record<string, any>) {
|
||||
return request.get({ url: '/article/list', data: data })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文章详情
|
||||
* @param { number } id
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getArticleDetail(data: { id: number }) {
|
||||
return request.get({ url: '/article/detail', data: data })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 加入收藏
|
||||
* @param { number } articleId
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function addCollect(data: { articleId: number }) {
|
||||
return request.post({ url: '/article/addCollect', data: data }, { isAuth: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 取消收藏
|
||||
* @param { number } id
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function cancelCollect(data: { articleId: number }) {
|
||||
return request.post({ url: '/article/cancelCollect', data: data }, { isAuth: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取收藏列表
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getCollect() {
|
||||
return request.get({ url: '/article/collect' })
|
||||
}
|
||||
28
uniapp/src/api/shop.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
//首页数据
|
||||
export function getIndex() {
|
||||
return request.get({ url: '/index' })
|
||||
}
|
||||
|
||||
// 装修页面
|
||||
export function getDecorate(data: any) {
|
||||
return request.get({ url: '/decorate', data })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 热门搜索
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getHotSearch() {
|
||||
return request.get({ url: '/hotSearch' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 搜索
|
||||
* @param { string } keyword 关键词
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getSearch(data: { keyword: string; pageNo: number; pageSize: number }) {
|
||||
return request.get({ url: '/search', data })
|
||||
}
|
||||
28
uniapp/src/api/user.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getUserCenter(header?: any) {
|
||||
return request.get({ url: '/user/center', header })
|
||||
}
|
||||
|
||||
// 个人信息
|
||||
export function getUserInfo() {
|
||||
return request.get({ url: '/user/info' }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 个人编辑
|
||||
export function userEdit(data: any) {
|
||||
return request.post({ url: '/user/edit', data }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 绑定手机
|
||||
export function userBindMobile(data: any, header?: any) {
|
||||
return request.post({ url: '/user/bindMobile', data, header }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 微信电话
|
||||
export function userMnpMobile(data: any) {
|
||||
return request.post({ url: '/user/mnpMobile', data }, { isAuth: true })
|
||||
}
|
||||
export function userChangePwd(data: any) {
|
||||
return request.post({ url: '/user/changePwd', data }, { isAuth: true })
|
||||
}
|
||||
0
uniapp/src/components/app/app.vue
Normal file
65
uniapp/src/components/news-card/news-card.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<navigator :url="`/pages/news_detail/news_detail?id=${newsId}`">
|
||||
<view class="news-card flex bg-white px-[20rpx] py-[32rpx]">
|
||||
<view class="mr-[20rpx]" v-if="item.image">
|
||||
<u-image :src="item.image" width="240" height="180"></u-image>
|
||||
</view>
|
||||
<view class="news-card-content flex flex-col justify-between flex-1">
|
||||
<view class="news-card-content-title text-lg font-medium">{{ item.title }}</view>
|
||||
<view class="news-card-content-intro text-gray-400 text-sm mt-[16rpx]">{{
|
||||
item.intro
|
||||
}}</view>
|
||||
|
||||
<view class="text-muted text-xs w-full flex justify-between mt-[12rpx]">
|
||||
<view>{{ item.createTime }}</view>
|
||||
<view class="flex items-center">
|
||||
<image
|
||||
src="/static/images/icon/icon_visit.png"
|
||||
class="w-[30rpx] h-[30rpx]"
|
||||
></image>
|
||||
<view class="ml-[10rpx]">{{ item.visit }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
item: any
|
||||
newsId: number
|
||||
}>(),
|
||||
{
|
||||
item: {},
|
||||
newsId: ''
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.news-card {
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
&-content {
|
||||
&-title {
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
&-intro {
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
uniapp/src/components/tab/tab.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<view
|
||||
:class="{ active, inactive: !active, tab: true }"
|
||||
:style="shouldShow ? '' : 'display: none;'"
|
||||
>
|
||||
<slot v-if="shouldRender"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, provide, inject, watch, computed, onMounted, getCurrentInstance } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
dot?: boolean | string
|
||||
name?: boolean | string
|
||||
info?: any
|
||||
}>(),
|
||||
{
|
||||
dot: false,
|
||||
name: ''
|
||||
}
|
||||
)
|
||||
|
||||
const active = ref<boolean>(false)
|
||||
const shouldShow = ref<boolean>(false)
|
||||
const shouldRender = ref<boolean>(false)
|
||||
const inited = ref(undefined)
|
||||
|
||||
const updateTabs: any = inject('updateTabs')
|
||||
const handleChange: any = inject('handleChange')
|
||||
|
||||
const updateRender = (value) => {
|
||||
inited.value = inited.value || value
|
||||
active.value = value
|
||||
shouldRender.value = inited.value!
|
||||
shouldShow.value = value
|
||||
}
|
||||
const update = () => {
|
||||
if (updateTabs) {
|
||||
updateTabs()
|
||||
}
|
||||
}
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
console.log(instance)
|
||||
handleChange(instance?.props, updateRender)
|
||||
|
||||
onMounted(() => {
|
||||
update()
|
||||
})
|
||||
|
||||
const changeData = computed(() => {
|
||||
const { dot, info } = props
|
||||
return {
|
||||
dot,
|
||||
info
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => changeData.value,
|
||||
() => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.name,
|
||||
(val) => {
|
||||
update()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tab.active {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tab.inactive {
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
38
uniapp/src/components/tabbar/tabbar.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<u-tabbar
|
||||
v-model="current"
|
||||
v-bind="tabbarStyle"
|
||||
:list="tabbarList"
|
||||
@change="handleChange"
|
||||
:hide-tab-bar="false"
|
||||
></u-tabbar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
import { computed, ref } from 'vue'
|
||||
const current = ref()
|
||||
const appStore = useAppStore()
|
||||
const tabbarList = computed(() => {
|
||||
return appStore.getTabbarConfig.map((item: any) => {
|
||||
const link = JSON.parse(item.link)
|
||||
return {
|
||||
iconPath: item.unselected,
|
||||
selectedIconPath: item.selected,
|
||||
text: item.name,
|
||||
link,
|
||||
pagePath: link.path
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const tabbarStyle = computed(() => ({
|
||||
activeColor: appStore.getStyleConfig.selectedColor,
|
||||
inactiveColor: appStore.getStyleConfig.defaultColor
|
||||
}))
|
||||
const handleChange = (index: number) => {
|
||||
const selectTab = tabbarList.value[index]
|
||||
navigateTo(selectTab.link, 'reLaunch')
|
||||
}
|
||||
</script>
|
||||
437
uniapp/src/components/tabs/tabs.vue
Normal file
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<view class="tabs">
|
||||
<u-sticky :enable="isFixed" :bg-color="stickyBgColor" :offset-top="top" :h5-nav-height="0">
|
||||
<view
|
||||
:id="id"
|
||||
:style="{
|
||||
background: bgColor
|
||||
}"
|
||||
>
|
||||
<scroll-view
|
||||
:style="{ height: height + 'rpx' }"
|
||||
scroll-x
|
||||
class="scroll-view"
|
||||
:scroll-left="scrollLeft"
|
||||
scroll-with-animation
|
||||
>
|
||||
<view class="scroll-box" :class="{ 'tabs-scorll-flex': !isScroll }">
|
||||
<view
|
||||
class="tab-item line1"
|
||||
:id="'tab-item-' + index"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@tap="clickTab(index)"
|
||||
:style="[tabItemStyle(index)]"
|
||||
>
|
||||
<u-badge
|
||||
:count="item[count] || item['dot'] || 0"
|
||||
:offset="offset"
|
||||
size="mini"
|
||||
></u-badge>
|
||||
{{ item[name] || item['name'] }}
|
||||
</view>
|
||||
<view v-if="showBar" class="tab-bar" :style="[tabBarStyle]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-sticky>
|
||||
<view
|
||||
class="tab-content"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchcancel="onTouchEnd"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<!-- <view class="tab-track" :class="{'tab-animated': animated}" :style="[trackStyle]"> -->
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<!-- </view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getRect } from '@/utils/util'
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
watch,
|
||||
provide,
|
||||
nextTick,
|
||||
onMounted,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
import { useTouch } from '@/hooks/useTouch'
|
||||
|
||||
// Touch 钩子
|
||||
const { touch, resetTouchStatus, touchStart, touchMove } = useTouch()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'change', value: number): void
|
||||
}>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
isScroll?: boolean // 导航菜单是否需要滚动,如只有2或者3个的时候,就不需要滚动了,此时使用flex平分tab的宽度
|
||||
current?: number | string // 当前活动tab的索引
|
||||
height?: number | string // 导航栏的高度和行高
|
||||
fontSize?: number | string // 字体大小
|
||||
duration?: number | string // 过渡动画时长, 单位ms
|
||||
activeColor?: number | string // 选中项的主题颜色
|
||||
inactiveColor?: number | string // 未选中项的颜色
|
||||
barWidth?: number | string // 菜单底部移动的bar的宽度,单位rpx
|
||||
barHeight?: number // 移动bar的高度
|
||||
gutter?: number | string // 单个tab的左或有内边距(左右相同)
|
||||
bgColor?: number | string // 导航栏的背景颜色
|
||||
name?: string // 读取传入的数组对象的属性(tab名称)
|
||||
count?: string // 读取传入的数组对象的属性(徽标数)
|
||||
offset?: number[] // 徽标数位置偏移
|
||||
bold?: boolean // 活动tab字体是否加粗
|
||||
activeItemStyle?: any // 当前活动tab item的样式
|
||||
showBar?: boolean // 是否显示底部的滑块
|
||||
barStyle?: any // 底部滑块的自定义样式
|
||||
itemWidth?: string // 标签的宽度
|
||||
isFixed?: boolean // 吸顶是否固定
|
||||
top?: number | string // 吸顶顶部距离
|
||||
stickyBgColor?: string // 吸顶颜色
|
||||
|
||||
swipeable: boolean // 是否允许滑动切换
|
||||
// animated: boolean // 切换动画
|
||||
}>(),
|
||||
{
|
||||
isScroll: true,
|
||||
current: 0,
|
||||
height: 80,
|
||||
fontSize: 28,
|
||||
duration: 0.3,
|
||||
activeColor: '#2073F4',
|
||||
inactiveColor: '#333',
|
||||
barWidth: 40,
|
||||
barHeight: 4,
|
||||
gutter: 30,
|
||||
bgColor: '#FFFFFF',
|
||||
name: 'name',
|
||||
count: 'count',
|
||||
offset: [5, 20],
|
||||
bold: true,
|
||||
activeItemStyle: {},
|
||||
showBar: true,
|
||||
barStyle: {},
|
||||
itemWidth: 'auto',
|
||||
isFixed: false,
|
||||
top: 0,
|
||||
stickyBgColor: '#FFFFFF',
|
||||
|
||||
swipeable: true
|
||||
// animated: true
|
||||
}
|
||||
)
|
||||
|
||||
const list = ref<any>([])
|
||||
const childrens = ref<any>([])
|
||||
const scrollLeft = ref<number>(0) // 滚动scroll-view的左边滚动距离
|
||||
const tabQueryInfo = ref<any>([]) // 存放对tab菜单查询后的节点信息
|
||||
const componentWidth = ref<number>(0) // 屏幕宽度,单位为px
|
||||
const scrollBarLeft = ref<number>(0) // 移动bar需要通过translateX()移动的距离
|
||||
const parentLeft = ref<number>(0) // 父元素(tabs组件)到屏幕左边的距离
|
||||
const id = ref<string>('cu-tab') // id值
|
||||
const currentIndex = ref<any>(props.current)
|
||||
const barFirstTimeMove = ref<boolean>(true) // 滑块第一次移动时(页面刚生成时),无需动画,否则给人怪异的感觉
|
||||
const swiping = ref<boolean>(false)
|
||||
|
||||
//@ts-ignore
|
||||
const ctx = getCurrentInstance()
|
||||
|
||||
// 监听tab的变化,重新计算tab菜单的布局信息,因为实际使用中菜单可能是通过
|
||||
// 后台获取的(如新闻app顶部的菜单),获取返回需要一定时间,所以list变化时,重新获取布局信息
|
||||
watch(
|
||||
() => list.value,
|
||||
async (n, o) => {
|
||||
// list变动时,重制内部索引,否则可能导致超出数组边界的情况
|
||||
if (!barFirstTimeMove.value && n.length !== o.length) {
|
||||
currentIndex.value = 0
|
||||
}
|
||||
// 用$nextTick等待视图更新完毕后再计算tab的局部信息,否则可能因为tab还没生成就获取,就会有问题
|
||||
await nextTick()
|
||||
init()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.current,
|
||||
(nVal, oVal) => {
|
||||
// 视图更新后再执行移动操作、
|
||||
nextTick(() => {
|
||||
currentIndex.value = nVal
|
||||
scrollByIndex()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 移动bar的样式
|
||||
const tabBarStyle = computed(() => {
|
||||
const style = {
|
||||
width: props.barWidth + 'rpx',
|
||||
transform: `translate(${scrollBarLeft.value}px, -100%)`,
|
||||
// 滑块在页面渲染后第一次滑动时,无需动画效果
|
||||
'transition-duration': `${barFirstTimeMove.value ? 0 : props.duration}s`,
|
||||
'background-color': props.activeColor,
|
||||
height: props.barHeight + 'rpx',
|
||||
opacity: barFirstTimeMove.value ? 0 : 1,
|
||||
// 设置一个很大的值,它会自动取能用的最大值,不用高度的一半,是因为高度可能是单数,会有小数出现
|
||||
'border-radius': `${props.barHeight / 2}px`
|
||||
}
|
||||
Object.assign(style, props.barStyle)
|
||||
return style
|
||||
})
|
||||
// tab的样式
|
||||
const tabItemStyle = computed(() => {
|
||||
return (index) => {
|
||||
let style: any = {
|
||||
height: props.height + 'rpx',
|
||||
'line-height': props.height + 'rpx',
|
||||
'font-size': props.fontSize + 'rpx',
|
||||
padding: props.isScroll ? `0 ${props.gutter}rpx` : '',
|
||||
flex: props.isScroll ? 'auto' : '1',
|
||||
width: `${props.itemWidth}rpx`
|
||||
}
|
||||
// 字体加粗
|
||||
if (index == currentIndex.value && props.bold) style.fontWeight = 'bold'
|
||||
if (index == currentIndex.value) {
|
||||
style.color = props.activeColor
|
||||
// 给选中的tab item添加外部自定义的样式
|
||||
style = Object.assign(style, props.activeItemStyle)
|
||||
} else {
|
||||
style.color = props.inactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
})
|
||||
|
||||
// const trackStyle = computed(() => {
|
||||
// if (!props.animated) return ''
|
||||
// return {
|
||||
// left: -100 * currentIndex.value + '%',
|
||||
// 'transition-duration': props.duration + 's',
|
||||
// '-webkit-transition-duration': props.duration + 's',
|
||||
// }
|
||||
// })
|
||||
|
||||
const updateTabs = () => {
|
||||
list.value = childrens.value.map((item) => {
|
||||
const { name, dot, active, inited } = item.event
|
||||
const { updateRender } = item
|
||||
return {
|
||||
name,
|
||||
dot,
|
||||
active,
|
||||
inited,
|
||||
updateRender
|
||||
}
|
||||
})
|
||||
// nextTick(() => {
|
||||
// init()
|
||||
// })
|
||||
}
|
||||
|
||||
// 设置一个init方法,方便多处调用
|
||||
const init = async () => {
|
||||
// 获取tabs组件的尺寸信息
|
||||
const tabRect = await getRect('#' + id.value, false, ctx)
|
||||
// tabs组件距离屏幕左边的宽度
|
||||
parentLeft.value = tabRect.left
|
||||
// tabs组件的宽度
|
||||
componentWidth.value = tabRect.width
|
||||
getTabRect()
|
||||
}
|
||||
|
||||
// 点击某一个tab菜单
|
||||
const clickTab = (index) => {
|
||||
// 点击当前活动tab,不触发事件
|
||||
if (index == currentIndex.value) return
|
||||
nextTick(() => {
|
||||
currentIndex.value = index
|
||||
scrollByIndex()
|
||||
})
|
||||
// 发送事件给父组件
|
||||
emit('change', index)
|
||||
}
|
||||
|
||||
// 查询tab的布局信息
|
||||
const getTabRect = () => {
|
||||
// 创建节点查询
|
||||
const query: any = uni.createSelectorQuery().in(ctx)
|
||||
// 历遍所有tab,这里是执行了查询,最终使用exec()会一次性返回查询的数组结果
|
||||
for (let i = 0; i < list.value.length; i++) {
|
||||
// 只要size和rect两个参数
|
||||
query.select(`#tab-item-${i}`).fields({
|
||||
size: true,
|
||||
rect: true
|
||||
})
|
||||
}
|
||||
// 执行查询,一次性获取多个结果
|
||||
query.exec((res) => {
|
||||
tabQueryInfo.value = res
|
||||
// 初始化滚动条和移动bar的位置
|
||||
scrollByIndex()
|
||||
})
|
||||
}
|
||||
|
||||
// 滚动scroll-view,让活动的tab处于屏幕的中间位置
|
||||
const scrollByIndex = () => {
|
||||
// 当前活动tab的布局信息,有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
|
||||
const tabInfo = tabQueryInfo.value[currentIndex.value]
|
||||
if (!tabInfo) return
|
||||
// 活动tab的宽度
|
||||
const tabWidth = tabInfo.width
|
||||
// 活动item的左边到tabs组件左边的距离,用item的left减去tabs的left
|
||||
const offsetLeft = tabInfo.left - parentLeft.value
|
||||
// 将活动的tabs-item移动到屏幕正中间,实际上是对scroll-view的移动
|
||||
const scrollLefts = offsetLeft - (componentWidth.value - tabWidth) / 2
|
||||
scrollLeft.value = scrollLefts < 0 ? 0 : scrollLefts
|
||||
// 当前活动item的中点点到左边的距离减去滑块宽度的一半,即可得到滑块所需的移动距离
|
||||
const left = tabInfo.left + tabInfo.width / 2 - parentLeft.value
|
||||
// 计算当前活跃item到组件左边的距离
|
||||
scrollBarLeft.value = left - uni.upx2px(props.barWidth) / 2
|
||||
// 第一次移动滑块的时候,barFirstTimeMove为true,放到延时中将其设置false
|
||||
// 延时是因为scrollBarLeft作用于computed计算时,需要一个过程需,否则导致出错
|
||||
if (barFirstTimeMove.value == true) {
|
||||
setTimeout(() => {
|
||||
barFirstTimeMove.value = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 更新子组件的显示
|
||||
childrens.value.forEach((item, ind) => {
|
||||
const active = ind === currentIndex.value
|
||||
if (active !== item.event.active || !item.event.inited) {
|
||||
item.updateRender(active)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 子组件调用此函数而产生的事件通信
|
||||
const handleChange = (event, updateRender) => {
|
||||
childrens.value.push({ event: event, updateRender })
|
||||
}
|
||||
// 手指触摸
|
||||
const onTouchStart = (event) => {
|
||||
if (!props.swipeable) return
|
||||
swiping.value = true
|
||||
touchStart(event)
|
||||
}
|
||||
// 手指滑动
|
||||
const onTouchMove = (event) => {
|
||||
if (!props.swipeable || !swiping.value) return
|
||||
touchMove(event)
|
||||
}
|
||||
// 手指滑动结束
|
||||
const onTouchEnd = () => {
|
||||
if (!props.swipeable || !swiping.value) return
|
||||
const minSwipeDistance = 50
|
||||
if (touch.direction === 'horizontal' && touch.offsetX >= minSwipeDistance) {
|
||||
let index,
|
||||
len = list.value.length,
|
||||
curIndex = currentIndex.value
|
||||
if (touch.deltaX <= 0) {
|
||||
curIndex >= len - 1 ? (index = 0) : (index = curIndex + 1)
|
||||
} else {
|
||||
curIndex <= 0 ? (index = len - 1) : (index = curIndex - 1)
|
||||
}
|
||||
nextTick(() => {
|
||||
currentIndex.value = index
|
||||
scrollByIndex()
|
||||
})
|
||||
// 发送事件给父组件
|
||||
emit('change', index)
|
||||
}
|
||||
swiping.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTabs()
|
||||
})
|
||||
|
||||
provide('handleChange', handleChange)
|
||||
provide('updateTabs', updateTabs)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.scroll-box {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tab-fixed {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
|
||||
scroll-view ::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.scroll-view {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
position: absolute;
|
||||
bottom: 6rpx;
|
||||
}
|
||||
|
||||
.tabs-scorll-flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
// .tab-content {
|
||||
// overflow: hidden;
|
||||
// .tab-track {
|
||||
// position: relative;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// }
|
||||
// .tab-animated {
|
||||
// display: flex;
|
||||
// transition-property: left;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
48
uniapp/src/components/widgets/banner/banner.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<view
|
||||
class="banner h-[340rpx] bg-white translate-y-0"
|
||||
v-if="content.data.length && content.enabled"
|
||||
>
|
||||
<swiper
|
||||
class="swiper h-full"
|
||||
:indicator-dots="content.data.length > 1"
|
||||
indicator-active-color="#4173ff"
|
||||
:autoplay="true"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
@click="handleClick(item.link)"
|
||||
>
|
||||
<u-image
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="getImageUrl(item.image)"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const { getImageUrl } = useAppStore()
|
||||
const handleClick = (link: any) => {
|
||||
navigateTo(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<view
|
||||
class="customer-service bg-white flex flex-col justify-center items-center mx-[36rpx] mt-[20rpx] rounded-lg px-[110rpx] pt-[100rpx] pb-[160rpx]"
|
||||
>
|
||||
<u-image width="280" height="280" :src="getImageUrl(content.qrcode)" />
|
||||
<view v-if="content.title" class="text-lg mt-[14rpx] font-medium">{{ content.title }}</view>
|
||||
<view v-if="content.time" class="text-content mt-[40rpx]"
|
||||
>服务时间:{{ content.time }}</view
|
||||
>
|
||||
<view v-if="content.mobile" class="text-content mt-[14rpx] flex flex-wrap">
|
||||
客服电话:{{ content.mobile }}
|
||||
<!-- #ifdef H5 -->
|
||||
<a class="ml-[10rpx] phone text-muted underline" :href="'tel:' + content.mobile">
|
||||
拨打
|
||||
</a>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<view class="ml-[10rpx] phone text-muted underline" @click="handleCall">拨打</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="mt-[100rpx] w-full">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
@click="saveImageToPhotosAlbum(getImageUrl(content.qrcode))"
|
||||
>
|
||||
保存二维码图片
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { saveImageToPhotosAlbum } from '@/utils/file'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { getImageUrl } = useAppStore()
|
||||
|
||||
const handleCall = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: String(props.content.mobile)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
56
uniapp/src/components/widgets/my-service/my-service.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="my-service bg-white mx-[20rpx] mt-[20rpx] rounded-lg">
|
||||
<div
|
||||
v-if="content.title"
|
||||
class="title px-[30rpx] py-[20rpx] font-medium text-xl border-light border-solid border-0 border-b"
|
||||
>
|
||||
<div>{{ content.title }}</div>
|
||||
</div>
|
||||
<div v-if="content.style == 1" class="flex flex-wrap pt-[40rpx] pb-[20rpx]">
|
||||
<div
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/4 mb-[15px]"
|
||||
@click="handleClick(item.link)"
|
||||
>
|
||||
<u-image width="52" height="52" :src="getImageUrl(item.image)" alt="" />
|
||||
<div class="mt-[7px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="content.style == 2">
|
||||
<div
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex items-center border-light border-solid border-0 border-b h-[100rpx] px-[24rpx]"
|
||||
@click="handleClick(item.link)"
|
||||
>
|
||||
<u-image width="48" height="48" :src="getImageUrl(item.image)" alt="" />
|
||||
<div class="ml-[20rpx] flex-1">{{ item.name }}</div>
|
||||
<div class="text-muted">
|
||||
<u-icon name="arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const { getImageUrl } = useAppStore()
|
||||
const handleClick = (link: any) => {
|
||||
navigateTo(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
38
uniapp/src/components/widgets/nav/nav.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<view class="nav pt-[30rpx] pb-[16rpx] bg-white" v-if="content.data.length && content.enabled">
|
||||
<view class="nav-item flex flex-wrap">
|
||||
<view
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/5 mb-[30rpx]"
|
||||
@click="handleClick(item.link)"
|
||||
>
|
||||
<u-image width="41px" height="41px" :src="getImageUrl(item.image)" alt="" />
|
||||
<view class="mt-[14rpx]">{{ item.name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const handleClick = (link: any) => {
|
||||
navigateTo(link)
|
||||
}
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
13
uniapp/src/components/widgets/search/search.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<navigator
|
||||
url="/pages/search/search"
|
||||
class="search px-[24rpx] py-[14rpx] bg-white"
|
||||
hover-class="none"
|
||||
>
|
||||
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
|
||||
</navigator>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style></style>
|
||||
49
uniapp/src/components/widgets/user-banner/user-banner.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<view
|
||||
class="banner h-[200rpx] mx-[20rpx] mt-[20rpx] translate-y-0"
|
||||
v-if="content.data.length && content.enabled"
|
||||
>
|
||||
<swiper
|
||||
class="swiper h-full"
|
||||
:indicator-dots="content.data.length > 1"
|
||||
indicator-active-color="#4173ff"
|
||||
:autoplay="true"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
@click="handleClick(item.limk)"
|
||||
>
|
||||
<u-image
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="getImageUrl(item.image)"
|
||||
:border-radius="14"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const handleClick = (link: any) => {
|
||||
navigateTo(link)
|
||||
}
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
60
uniapp/src/components/widgets/user-info/user-info.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<view class="user-info flex px-[50rpx] justify-between py-[50rpx]">
|
||||
<view
|
||||
v-if="isLogin"
|
||||
class="flex items-center"
|
||||
@click="navigateTo('/pages/user_data/user_data')"
|
||||
>
|
||||
<u-avatar :src="user.avatar" :size="120"></u-avatar>
|
||||
<view class="text-white ml-[20rpx]">
|
||||
<view class="text-2xl">{{ user.nickname }}</view>
|
||||
<view class="text-xs mt-[18rpx]" @click.stop="copy(user.username)">
|
||||
账号:{{ user.username }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<navigator v-else class="flex items-center" hover-class="none" url="/pages/login/login">
|
||||
<u-avatar src="/static/images/user/default_avatar.png" :size="120"></u-avatar>
|
||||
<view class="text-white text-3xl ml-[20rpx]">未登录</view>
|
||||
</navigator>
|
||||
<navigator v-if="isLogin" hover-class="none" url="/pages/user_set/user_set">
|
||||
<u-icon name="setting" color="#fff" :size="48"></u-icon>
|
||||
</navigator>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useCopy } from '@/hooks/useCopy'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
isLogin: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
const { copy } = useCopy()
|
||||
const navigateTo = (url: string) => {
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-info {
|
||||
background: url(/static/images/user/my_topbg.png);
|
||||
height: 115px;
|
||||
background-position: bottom;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
</style>
|
||||
5
uniapp/src/enums/agreementEnums.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
//菜单主题类型
|
||||
export enum AgreementEnum {
|
||||
PRIVACY = 'privacy',
|
||||
SERVICE = 'service'
|
||||
}
|
||||
34
uniapp/src/enums/appEnums.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
//菜单主题类型
|
||||
export enum ThemeEnum {
|
||||
LIGHT = 'light',
|
||||
DARK = 'dark'
|
||||
}
|
||||
|
||||
// 客户端
|
||||
export enum ClientEnum {
|
||||
MP_WEIXIN = 1, // 微信-小程序
|
||||
OA_WEIXIN = 2, // 微信-公众号
|
||||
H5 = 3, // H5
|
||||
IOS = 5, //苹果
|
||||
ANDROID = 6 //安卓
|
||||
}
|
||||
|
||||
export enum SMSEnum {
|
||||
LOGIN = 101,
|
||||
BIND_MOBILE = 102,
|
||||
CHANGE_MOBILE = 103,
|
||||
FIND_PASSWORD = 104
|
||||
}
|
||||
|
||||
export enum SearchTypeEnum {
|
||||
HISTORY = 'history'
|
||||
}
|
||||
|
||||
// 用户资料
|
||||
export enum FieldType {
|
||||
NONE = '',
|
||||
AVATAR = 'avatar',
|
||||
USERNAME = 'username',
|
||||
NICKNAME = 'nickname',
|
||||
SEX = 'sex'
|
||||
}
|
||||
9
uniapp/src/enums/cacheEnums.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// 本地缓冲key
|
||||
|
||||
//token
|
||||
export const TOKEN_KEY = 'token'
|
||||
|
||||
// 搜索历史记录
|
||||
export const HISTORY = 'history'
|
||||
|
||||
export const BACK_URL = 'back_url'
|
||||
28
uniapp/src/enums/requestEnums.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export enum ContentTypeEnum {
|
||||
// json
|
||||
JSON = 'application/json;charset=UTF-8',
|
||||
// form-data 上传资源(图片,视频)
|
||||
FORM_DATA = 'multipart/form-data;charset=UTF-8'
|
||||
}
|
||||
|
||||
export enum RequestMethodsEnum {
|
||||
GET = 'GET',
|
||||
POST = 'POST'
|
||||
}
|
||||
|
||||
export enum RequestCodeEnum {
|
||||
SUCCESS = 200, //成功
|
||||
FAILED = 300, // 失败
|
||||
PARAMS_VALID_ERROR = 310, //参数校验错误
|
||||
PARAMS_TYPE_ERROR = 311, //参数类型错误
|
||||
REQUEST_METHOD_ERROR = 312, //请求方法错误
|
||||
ASSERT_ARGUMENT_ERROR = 313, //断言参数错误
|
||||
ASSERT_MYBATIS_ERROR = 314, //断言mybatis错误
|
||||
LOGIN_ACCOUNT_ERROR = 330, //登陆账号或密码错误
|
||||
LOGIN_DISABLE_ERROR = 331, //登陆账号已被禁用
|
||||
TOKEN_EMPTY = 332, // TOKEN参数为空
|
||||
TOKEN_INVALID = 333, // TOKEN参数无效
|
||||
NO_PERMISSTION = 403, //无相关权限
|
||||
REQUEST_404_ERROR = 404, //请求接口不存在
|
||||
SYSTEM_ERROR = 500 //系统错误
|
||||
}
|
||||
10
uniapp/src/hooks/useCopy.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function useCopy() {
|
||||
const copy = (text: string) => {
|
||||
uni.setClipboardData({
|
||||
data: String(text)
|
||||
})
|
||||
}
|
||||
return {
|
||||
copy
|
||||
}
|
||||
}
|
||||
21
uniapp/src/hooks/useLockFn.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
|
||||
const isLock = ref(false)
|
||||
const lockFn = async (...args: any[]) => {
|
||||
if (isLock.value) return
|
||||
isLock.value = true
|
||||
try {
|
||||
const res = await fn(...args)
|
||||
isLock.value = false
|
||||
return res
|
||||
} catch (e) {
|
||||
isLock.value = false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLock,
|
||||
lockFn
|
||||
}
|
||||
}
|
||||
72
uniapp/src/hooks/useTouch.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
/**
|
||||
* @description 触碰屏幕钩子函数
|
||||
* @return { Function } 暴露钩子
|
||||
*/
|
||||
export function useTouch() {
|
||||
// 最小移动距离
|
||||
const MIN_DISTANCE = 10
|
||||
|
||||
const touch = reactive({
|
||||
direction: '',
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 计算距离
|
||||
* @return { string } 空字符串
|
||||
*/
|
||||
const getDirection = (x: number, y: number) => {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal'
|
||||
}
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重置参数
|
||||
*/
|
||||
const resetTouchStatus = () => {
|
||||
touch.direction = ''
|
||||
touch.deltaX = 0
|
||||
touch.deltaY = 0
|
||||
touch.offsetX = 0
|
||||
touch.offsetY = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 触发
|
||||
*/
|
||||
const touchStart = (event: any) => {
|
||||
resetTouchStatus()
|
||||
const events = event.touches[0]
|
||||
touch.startX = events.clientX
|
||||
touch.startY = events.clientY
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移动
|
||||
*/
|
||||
const touchMove = (event: any) => {
|
||||
const events = event.touches[0]
|
||||
touch.deltaX = events.clientX - touch.startX
|
||||
touch.deltaY = events.clientY - touch.startY
|
||||
touch.offsetX = Math.abs(touch.deltaX)
|
||||
touch.offsetY = Math.abs(touch.deltaY)
|
||||
touch.direction = touch.direction || getDirection(touch.offsetX, touch.offsetY)
|
||||
}
|
||||
|
||||
return {
|
||||
touch,
|
||||
resetTouchStatus,
|
||||
touchStart,
|
||||
touchMove
|
||||
}
|
||||
}
|
||||
16
uniapp/src/main.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import plugins from './plugins'
|
||||
import { setupRouter } from './router'
|
||||
import './styles/index.scss'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
setupRouter()
|
||||
})
|
||||
app.use(plugins)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
78
uniapp/src/manifest.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "",
|
||||
"appid": "",
|
||||
"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": "wx65b3824de0b3d3b0",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"h5": {
|
||||
"router": {
|
||||
"mode": "history"
|
||||
},
|
||||
"title": "加载中"
|
||||
}
|
||||
}
|
||||
135
uniapp/src/pages.json
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/news/news",
|
||||
"style": {
|
||||
"navigationBarTitleText": "资讯"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/user",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/forget_pwd/forget_pwd",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/customer_service/customer_service",
|
||||
"style": {
|
||||
"navigationBarTitleText": "联系客服"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/news_detail/news_detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user_set/user_set",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人设置"
|
||||
},
|
||||
"auth": true
|
||||
},
|
||||
{
|
||||
"path": "pages/collection/collection",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的收藏"
|
||||
},
|
||||
"auth": true
|
||||
},
|
||||
{
|
||||
"path": "pages/as_us/as_us",
|
||||
"style": {
|
||||
"navigationBarTitleText": "关于我们"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/agreement/agreement",
|
||||
"style": {
|
||||
"navigationBarTitleText": "协议"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/change_password/change_password",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改密码"
|
||||
},
|
||||
"auth": true
|
||||
},
|
||||
{
|
||||
"path": "pages/user_data/user_data",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料"
|
||||
},
|
||||
"auth": true
|
||||
},
|
||||
{
|
||||
"path": "pages/search/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/webview/webview"
|
||||
},
|
||||
{
|
||||
"path": "pages/bind_mobile/bind_mobile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绑定手机号"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/empty/empty",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "uni_modules/vk-uview-ui/components/u-avatar-cropper/u-avatar-cropper",
|
||||
"style": {
|
||||
"navigationBarTitleText": "头像裁剪",
|
||||
"navigationBarBackgroundColor": "#000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "商城",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"h5": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
"easycom": {
|
||||
"custom": {
|
||||
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue",
|
||||
"^w-(.*)": "@/components/widgets/$1/$1.vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
35
uniapp/src/pages/agreement/agreement.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<u-parse :html="agreementContent"></u-parse>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { AgreementEnum } from '@/enums/agreementEnums'
|
||||
import { getPolicy } from '@/api/app'
|
||||
|
||||
let agreementType = ref('') // 协议类型
|
||||
const agreementContent = ref('') // 协议内容
|
||||
|
||||
const getData = async (type) => {
|
||||
const res = await getPolicy({ type })
|
||||
console.log(res, 'res')
|
||||
|
||||
agreementContent.value = res.content
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: res.name
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (options.type) {
|
||||
agreementType = options.type
|
||||
getData(agreementType)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
22
uniapp/src/pages/as_us/as_us.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<view class="as-us flex flex-1 flex-col items-center">
|
||||
<image :src="appStore.config.website.logo" mode="" class="img"></image>
|
||||
<view class="text-content mt-[20rpx]">当前版本{{ appStore.config.version }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.as-us {
|
||||
.img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 96rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
uniapp/src/pages/bind_mobile/bind_mobile.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<view class="bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border">
|
||||
<view class="w-full">
|
||||
<u-form borderBottom :label-width="150">
|
||||
<u-form-item label="手机号" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.mobile"
|
||||
:border="false"
|
||||
placeholder="请输入手机号码"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="验证码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.code"
|
||||
placeholder="请输入验证码"
|
||||
:border="false"
|
||||
/>
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
|
||||
@click="sendSms"
|
||||
>
|
||||
<u-verification-code
|
||||
ref="uCodeRef"
|
||||
:seconds="60"
|
||||
@change="codeChange"
|
||||
change-text="x秒"
|
||||
/>
|
||||
{{ codeTips }}
|
||||
</view>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<view class="mt-[40rpx]">
|
||||
<u-button type="primary" shape="circle" @click="handleConfirm"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { userBindMobile } from '@/api/user'
|
||||
import { smsSend } from '@/api/app'
|
||||
import { SMSEnum } from '@/enums/appEnums'
|
||||
import { reactive, ref, shallowRef } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
const uCodeRef = shallowRef()
|
||||
const codeTips = ref('')
|
||||
|
||||
const userStore = useUserStore()
|
||||
const codeChange = (text: string) => {
|
||||
codeTips.value = text
|
||||
}
|
||||
|
||||
const formData = reactive({
|
||||
type: 'bind',
|
||||
mobile: '',
|
||||
code: ''
|
||||
})
|
||||
const sendSms = async () => {
|
||||
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
|
||||
if (uCodeRef.value?.canGetCode) {
|
||||
await smsSend({
|
||||
scene: SMSEnum.BIND_MOBILE,
|
||||
mobile: formData.mobile
|
||||
})
|
||||
uni.$u.toast('发送成功')
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
const handleConfirm = async () => {
|
||||
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
|
||||
if (!formData.code) return uni.$u.toast('请输入验证码')
|
||||
await userBindMobile(formData, { token: userStore.temToken })
|
||||
uni.$u.toast('绑定成功')
|
||||
userStore.login(userStore.temToken!)
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
80
uniapp/src/pages/change_password/change_password.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<view
|
||||
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[100rpx] box-border"
|
||||
>
|
||||
<view class="w-full">
|
||||
<view class="text-2xl font-medium mb-[60rpx]">
|
||||
{{ type == 'set' ? '设置登录密码' : '修改登录密码' }}
|
||||
</view>
|
||||
<u-form borderBottom :label-width="150">
|
||||
<u-form-item label="原密码" borderBottom v-if="type != 'set'">
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.oldPassword"
|
||||
:border="false"
|
||||
placeholder="请输入原来的密码"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="新密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password"
|
||||
placeholder="6-20位数字+字母或符号组合"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="确认密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password2"
|
||||
placeholder="再次输入新密码"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<view class="mt-[100rpx]">
|
||||
<u-button type="primary" shape="circle" @click="handleConfirm"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { userChangePwd } from '@/api/user'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
const type = ref('')
|
||||
const formData = reactive<any>({
|
||||
password: '',
|
||||
password2: ''
|
||||
})
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!formData.oldPassword && type.value != 'set') return uni.$u.toast('请输入原来的密码')
|
||||
if (!formData.password) return uni.$u.toast('请输入密码')
|
||||
if (!formData.password2) return uni.$u.toast('请输入确认密码')
|
||||
if (formData.password != formData.password2) return uni.$u.toast('两次输入的密码不一致')
|
||||
await userChangePwd(formData)
|
||||
uni.$u.toast('操作成功')
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
type.value = options.type || ''
|
||||
if (type.value == 'set') {
|
||||
uni.setNavigationBarTitle({
|
||||
title: '设置登录密码'
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
62
uniapp/src/pages/collection/collection.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<z-paging
|
||||
ref="paging"
|
||||
v-model="collectData"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
use-page-scroll
|
||||
>
|
||||
<u-swipe-action
|
||||
:show="item.show"
|
||||
:index="index"
|
||||
v-for="(item, index) in collectData"
|
||||
:key="item.id"
|
||||
@click="handleCollect"
|
||||
:options="options"
|
||||
btn-width="120"
|
||||
>
|
||||
<news-card :item="item" :newsId="item.articleId"></news-card>
|
||||
</u-swipe-action>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, shallowRef } from 'vue'
|
||||
import { getCollect, cancelCollect } from '@/api/news'
|
||||
|
||||
const paging = shallowRef()
|
||||
const options = reactive([
|
||||
{
|
||||
text: '取消收藏',
|
||||
style: {
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: '#FF2C3C'
|
||||
}
|
||||
}
|
||||
])
|
||||
const collectData: any = ref([])
|
||||
|
||||
const queryList = async (pageNo, pageSize) => {
|
||||
const { lists } = await getCollect()
|
||||
lists.forEach((item) => {
|
||||
item.show = false
|
||||
})
|
||||
collectData.value = lists
|
||||
paging.value.complete(lists)
|
||||
}
|
||||
|
||||
const handleCollect = async (index: number): Promise<void> => {
|
||||
try {
|
||||
const articleId: number = collectData.value[index].articleId
|
||||
await cancelCollect({ articleId })
|
||||
uni.$u.toast('已取消收藏')
|
||||
paging.value.reload()
|
||||
} catch (err) {
|
||||
//TODO handle the exception
|
||||
console.log('取消收藏报错=>', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
26
uniapp/src/pages/customer_service/customer_service.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view class="customer-service">
|
||||
<view v-for="(item, index) in state.pages" :key="index">
|
||||
<template v-if="item.name == 'customer-service'">
|
||||
<w-customer-service :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getDecorate } from '@/api/shop'
|
||||
import { reactive } from 'vue'
|
||||
const state = reactive<{
|
||||
pages: any[]
|
||||
}>({
|
||||
pages: []
|
||||
})
|
||||
const getData = async () => {
|
||||
const data = await getDecorate({ id: 3 })
|
||||
state.pages = JSON.parse(data.pages)
|
||||
}
|
||||
getData()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
7
uniapp/src/pages/empty/empty.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style></style>
|
||||
110
uniapp/src/pages/forget_pwd/forget_pwd.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view
|
||||
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[100rpx] box-border"
|
||||
>
|
||||
<view class="w-full">
|
||||
<view class="text-2xl font-medium mb-[60rpx]">忘记登录密码</view>
|
||||
<u-form borderBottom :label-width="150">
|
||||
<u-form-item label="手机号" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.mobile"
|
||||
:border="false"
|
||||
placeholder="请输入手机号码"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="验证码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.code"
|
||||
placeholder="请输入验证码"
|
||||
:border="false"
|
||||
/>
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
|
||||
@click="sendSms"
|
||||
>
|
||||
<u-verification-code
|
||||
ref="uCodeRef"
|
||||
:seconds="60"
|
||||
@change="codeChange"
|
||||
change-text="x秒"
|
||||
/>
|
||||
<text :class="formData.mobile ? 'text-primary' : 'text-muted'">
|
||||
{{ codeTips }}
|
||||
</text>
|
||||
</view>
|
||||
</u-form-item>
|
||||
<u-form-item label="新密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password"
|
||||
placeholder="6-20位数字+字母或符号组合"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="确认密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password2"
|
||||
placeholder="再次输入新密码"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<view class="mt-[100rpx]">
|
||||
<u-button type="primary" shape="circle" @click="handleConfirm"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { forgotPassword } from '@/api/account'
|
||||
import { smsSend } from '@/api/app'
|
||||
import { SMSEnum } from '@/enums/appEnums'
|
||||
import { reactive, ref, shallowRef } from 'vue'
|
||||
|
||||
const uCodeRef = shallowRef()
|
||||
const codeTips = ref('')
|
||||
const formData = reactive({
|
||||
mobile: '',
|
||||
code: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
})
|
||||
|
||||
const codeChange = (text: string) => {
|
||||
codeTips.value = text
|
||||
}
|
||||
|
||||
const sendSms = async () => {
|
||||
if (!formData.mobile) return
|
||||
if (uCodeRef.value?.canGetCode) {
|
||||
await smsSend({
|
||||
scene: SMSEnum.FIND_PASSWORD,
|
||||
mobile: formData.mobile
|
||||
})
|
||||
uni.$u.toast('发送成功')
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
|
||||
if (!formData.password) return uni.$u.toast('请输入密码')
|
||||
if (!formData.password2) return uni.$u.toast('请输入确认密码')
|
||||
if (formData.password != formData.password2) return uni.$u.toast('两次输入的密码不一致')
|
||||
await forgotPassword(formData)
|
||||
uni.$u.toast('操作成功')
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
61
uniapp/src/pages/index/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<view class="index">
|
||||
<view v-for="(item, index) in state.pages" :key="index">
|
||||
<template v-if="item.name == 'search'">
|
||||
<w-search :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'banner'">
|
||||
<w-banner :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'nav'">
|
||||
<w-nav :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
</view>
|
||||
<view class="article" v-if="state.article.length">
|
||||
<view
|
||||
class="flex items-center article-title mx-[20rpx] my-[30rpx] text-2xl font-medium"
|
||||
>
|
||||
最新资讯
|
||||
</view>
|
||||
<news-card
|
||||
v-for="item in state.article"
|
||||
:key="item.id"
|
||||
:news-id="item.id"
|
||||
:item="item"
|
||||
/>
|
||||
</view>
|
||||
<tabbar />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIndex } from '@/api/shop'
|
||||
import { reactive } from 'vue'
|
||||
const state = reactive<{
|
||||
pages: any[]
|
||||
article: any[]
|
||||
}>({
|
||||
pages: [],
|
||||
article: []
|
||||
})
|
||||
const getData = async () => {
|
||||
const data = await getIndex()
|
||||
state.pages = JSON.parse(data.pages)
|
||||
state.article = data.article
|
||||
}
|
||||
|
||||
getData()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.article-title {
|
||||
&::before {
|
||||
content: '';
|
||||
width: 8rpx;
|
||||
height: 34rpx;
|
||||
display: block;
|
||||
margin-right: 10rpx;
|
||||
background: $u-type-primary;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
380
uniapp/src/pages/login/login.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<view
|
||||
class="bg-white login min-h-full flex flex-col items-center px-[40rpx] pt-[80rpx] box-border"
|
||||
>
|
||||
<view>
|
||||
<u-image :src="appStore.config.website.logo" mode="widthFix" height="160" width="160" />
|
||||
</view>
|
||||
<view class="mt-4 text-xl font-medium">{{ appStore.config.website.name }}</view>
|
||||
<view class="w-full mt-[60rpx] pb-[60rpx]">
|
||||
<u-form borderBottom>
|
||||
<template
|
||||
v-if="loginWay == LoginWayEnum.ACCOUNT && includeLoginWay(LoginWayEnum.ACCOUNT)"
|
||||
>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon/icon_user.png" />
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.username"
|
||||
:border="false"
|
||||
placeholder="请输入账号/手机号码"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon
|
||||
class="mr-2"
|
||||
:size="36"
|
||||
name="/static/images/icon/icon_password.png"
|
||||
/>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
:border="false"
|
||||
/>
|
||||
<navigator url="/pages/forget_pwd/forget_pwd" hover-class="none">
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3"
|
||||
>
|
||||
忘记密码?
|
||||
</view>
|
||||
</navigator>
|
||||
</u-form-item>
|
||||
</template>
|
||||
<template
|
||||
v-if="loginWay == LoginWayEnum.MOBILE && includeLoginWay(LoginWayEnum.MOBILE)"
|
||||
>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon
|
||||
class="mr-2"
|
||||
:size="36"
|
||||
name="/static/images/icon/icon_mobile.png"
|
||||
/>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.mobile"
|
||||
:border="false"
|
||||
placeholder="请输入手机号码"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon/icon_code.png" />
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.code"
|
||||
placeholder="请输入验证码"
|
||||
:border="false"
|
||||
/>
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 leading-4 ml-3 w-[180rpx]"
|
||||
@click="sendSms"
|
||||
>
|
||||
<u-verification-code
|
||||
ref="uCodeRef"
|
||||
:seconds="60"
|
||||
@change="codeChange"
|
||||
change-text="x秒"
|
||||
/>
|
||||
<text :class="formData.mobile ? 'text-primary' : 'text-muted'">
|
||||
{{ codeTips }}
|
||||
</text>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</template>
|
||||
</u-form>
|
||||
<view class="mt-[40rpx]" v-if="isOpenAgreement">
|
||||
<u-checkbox v-model="isCheckAgreement" shape="circle">
|
||||
<view class="text-xs flex">
|
||||
已阅读并同意
|
||||
<navigator
|
||||
@click.stop=""
|
||||
class="text-primary"
|
||||
hover-class="none"
|
||||
url="/pages/agreement/agreement?type=service"
|
||||
>
|
||||
《服务协议》
|
||||
</navigator>
|
||||
和<navigator
|
||||
@click.stop=""
|
||||
class="text-primary"
|
||||
hover-class="none"
|
||||
url="/pages/agreement/agreement?type=privacy"
|
||||
>
|
||||
《隐私协议》
|
||||
</navigator>
|
||||
</view>
|
||||
</u-checkbox>
|
||||
</view>
|
||||
<view class="mt-[60rpx]">
|
||||
<u-button type="primary" shape="circle" @click="handleLogin(formData.scene)">
|
||||
登 录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<view class="text-content flex justify-between mt-[40rpx]">
|
||||
<view class="flex-1">
|
||||
<view
|
||||
v-if="
|
||||
loginWay == LoginWayEnum.MOBILE && includeLoginWay(LoginWayEnum.ACCOUNT)
|
||||
"
|
||||
@click="changeLoginWay(LoginTypeEnum.ACCOUNT, LoginWayEnum.ACCOUNT)"
|
||||
>
|
||||
账号密码登录
|
||||
</view>
|
||||
<view
|
||||
v-if="
|
||||
loginWay == LoginWayEnum.ACCOUNT && includeLoginWay(LoginWayEnum.MOBILE)
|
||||
"
|
||||
@click="changeLoginWay(LoginTypeEnum.MOBILE, LoginWayEnum.MOBILE)"
|
||||
>
|
||||
短信验证码登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<navigator url="/pages/register/register" hover-class="none">注册账号</navigator>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN || H5 -->
|
||||
<view class="mt-[80rpx]" v-if="isOpenOtherAuth && isWeixin">
|
||||
<u-divider>第三方登录</u-divider>
|
||||
<div class="flex justify-center mt-[40rpx]">
|
||||
<div
|
||||
v-if="includeAuthWay(LoginAuthEnum.WX) && isWeixin"
|
||||
class="flex flex-col items-center"
|
||||
@click="wxLogin"
|
||||
>
|
||||
<img src="@/static/images/icon/icon_wx.png" class="w-[80rpx] h-[80rpx]" />
|
||||
<div class="text-sm mt-[10px]">微信登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { login } from '@/api/account'
|
||||
import { smsSend } from '@/api/app'
|
||||
import { SMSEnum } from '@/enums/appEnums'
|
||||
import { BACK_URL } from '@/enums/cacheEnums'
|
||||
import { useLockFn } from '@/hooks/useLockFn'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import cache from '@/utils/cache'
|
||||
import { isWeixinClient } from '@/utils/client'
|
||||
import { currentPage } from '@/utils/util'
|
||||
// #ifdef H5
|
||||
import wechatOa from '@/utils/wechat'
|
||||
// #endif
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import { computed, reactive, ref, shallowRef, watch } from 'vue'
|
||||
enum LoginTypeEnum {
|
||||
MOBILE = 'mobile',
|
||||
ACCOUNT = 'account',
|
||||
MNP = 'mnp'
|
||||
}
|
||||
|
||||
enum LoginWayEnum {
|
||||
ACCOUNT = 1,
|
||||
MOBILE = 2
|
||||
}
|
||||
|
||||
enum LoginAuthEnum {
|
||||
WX = 1,
|
||||
QQ = 2
|
||||
}
|
||||
const isWeixin = ref(true)
|
||||
// #ifdef H5
|
||||
isWeixin.value = isWeixinClient()
|
||||
// #endif
|
||||
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const uCodeRef = shallowRef()
|
||||
const loginWay = ref<LoginWayEnum>()
|
||||
const codeTips = ref('')
|
||||
const isCheckAgreement = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
scene: '',
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
mobile: ''
|
||||
})
|
||||
|
||||
const codeChange = (text: string) => {
|
||||
codeTips.value = text
|
||||
}
|
||||
|
||||
const sendSms = async () => {
|
||||
if (!formData.mobile) return
|
||||
if (uCodeRef.value?.canGetCode) {
|
||||
await smsSend({
|
||||
scene: SMSEnum.LOGIN,
|
||||
mobile: formData.mobile
|
||||
})
|
||||
uni.$u.toast('发送成功')
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
|
||||
const changeLoginWay = (type: LoginTypeEnum, way: LoginWayEnum) => {
|
||||
formData.scene = type
|
||||
loginWay.value = way
|
||||
}
|
||||
|
||||
const includeLoginWay = (way: LoginWayEnum) => {
|
||||
return appStore.getLoginConfig.loginWay.includes(way)
|
||||
}
|
||||
|
||||
const includeAuthWay = (way: LoginAuthEnum) => {
|
||||
return appStore.getLoginConfig.autoLoginAuth.includes(way)
|
||||
}
|
||||
|
||||
const isOpenAgreement = computed(() => appStore.getLoginConfig.openAgreement == 1)
|
||||
|
||||
const isOpenOtherAuth = computed(() => appStore.getLoginConfig.openOtherAuth == 1)
|
||||
const isForceBindMobile = computed(() => appStore.getLoginConfig.forceBindMobile == 1)
|
||||
|
||||
const loginFun = async (scene: LoginTypeEnum, code?: string) => {
|
||||
if (!isCheckAgreement.value && isOpenAgreement.value)
|
||||
return uni.$u.toast('请勾选已阅读并同意《服务协议》和《隐私协议》')
|
||||
if (scene == LoginTypeEnum.ACCOUNT) {
|
||||
if (!formData.username) return uni.$u.toast('请输入账号/手机号码')
|
||||
if (!formData.password) return uni.$u.toast('请输入密码')
|
||||
}
|
||||
if (scene == LoginTypeEnum.MOBILE) {
|
||||
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
|
||||
if (!formData.code) return uni.$u.toast('请输入验证码')
|
||||
}
|
||||
const params = {
|
||||
...formData,
|
||||
scene
|
||||
}
|
||||
if (code) params.code = code
|
||||
uni.showLoading({
|
||||
title: '请稍后...'
|
||||
})
|
||||
try {
|
||||
const data = await login(params)
|
||||
loginHandle(data)
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
uni.$u.toast(error)
|
||||
}
|
||||
}
|
||||
|
||||
const loginHandle = async (data: any) => {
|
||||
const { token, isBindMobile } = data
|
||||
if (!isBindMobile && isForceBindMobile.value) {
|
||||
userStore.temToken = token
|
||||
uni.navigateTo({
|
||||
url: '/pages/bind_mobile/bind_mobile'
|
||||
})
|
||||
uni.hideLoading()
|
||||
return
|
||||
}
|
||||
userStore.login(data.token)
|
||||
await userStore.getUser()
|
||||
uni.$u.toast('登录成功')
|
||||
uni.hideLoading()
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack({
|
||||
success: () => {
|
||||
// @ts-ignore
|
||||
const { onLoad, options } = currentPage()
|
||||
// 刷新上一个页面
|
||||
onLoad && onLoad(options)
|
||||
}
|
||||
})
|
||||
} else if (cache.get(BACK_URL)) {
|
||||
console.log(BACK_URL, cache.get(BACK_URL))
|
||||
uni.redirectTo({ url: cache.get(BACK_URL) })
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
cache.remove(BACK_URL)
|
||||
}
|
||||
|
||||
const { lockFn: handleLogin } = useLockFn(loginFun)
|
||||
|
||||
const wxLogin = async () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
const data: any = await uni.login({
|
||||
provider: 'weixin'
|
||||
})
|
||||
handleLogin(LoginTypeEnum.MNP, data.code)
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (isWeixin.value) {
|
||||
wechatOa.getUrl()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
watch(
|
||||
() => appStore.getLoginConfig,
|
||||
(value) => {
|
||||
if (value.loginWay) {
|
||||
loginWay.value = value.loginWay[0]
|
||||
//@ts-ignore
|
||||
formData.scene = LoginTypeEnum[LoginWayEnum[loginWay.value]]
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onShow(async () => {
|
||||
try {
|
||||
if (userStore.isLogin) {
|
||||
uni.showLoading({
|
||||
title: '请稍后...'
|
||||
})
|
||||
await userStore.getUser()
|
||||
uni.hideLoading()
|
||||
uni.navigateBack()
|
||||
}
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
})
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (userStore.isLogin) {
|
||||
// 已经登录 => 首页
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
return
|
||||
}
|
||||
// #ifdef H5
|
||||
const { code } = options
|
||||
if (code) {
|
||||
uni.showLoading({
|
||||
title: '请稍后...'
|
||||
})
|
||||
|
||||
try {
|
||||
const data = await wechatOa.authLogin(code)
|
||||
loginHandle(data)
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
65
uniapp/src/pages/news/component/news-list.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
:auto="i == index"
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:data-key="i"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<block v-for="(newsItem, newsIndex) in dataList" :key="newsIndex">
|
||||
<news-card :item="newsItem" :newsId="newsItem.id"></news-card>
|
||||
</block>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, shallowRef } from 'vue'
|
||||
import { getArticleList } from '@/api/news'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
cid: number
|
||||
i: number
|
||||
index: number
|
||||
}>(),
|
||||
{
|
||||
cid: 0
|
||||
}
|
||||
)
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref([])
|
||||
const isFirst = ref<boolean>(true)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (props.i == props.index && isFirst.value) {
|
||||
isFirst.value = false
|
||||
paging.value?.reload()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const queryList = async (pageNo, pageSize) => {
|
||||
try {
|
||||
const { lists } = await getArticleList({
|
||||
cid: props.cid,
|
||||
pageNo,
|
||||
pageSize
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
60
uniapp/src/pages/news/news.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<view class="news">
|
||||
<!-- 搜索 -->
|
||||
<navigator class="news-search px-[24rpx] py-[14rpx] bg-white" url="/pages/search/search">
|
||||
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
|
||||
</navigator>
|
||||
|
||||
<!-- 内容 -->
|
||||
<tabs
|
||||
:current="current"
|
||||
@change="handleChange"
|
||||
height="80"
|
||||
bar-width="60"
|
||||
:barStyle="{ bottom: '0' }"
|
||||
>
|
||||
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
|
||||
<view class="news-list pt-[20rpx]">
|
||||
<news-list :cid="item.id" :i="i" :index="current"></news-list>
|
||||
</view>
|
||||
</tab>
|
||||
</tabs>
|
||||
<tabbar />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { onLoad, onShow, onReady } from '@dcloudio/uni-app'
|
||||
import NewsList from './component/news-list.vue'
|
||||
import { getArticleCate } from '@/api/news'
|
||||
|
||||
const tabList = ref<any>([])
|
||||
const current = ref<number>(0)
|
||||
|
||||
const handleChange = (index: number) => {
|
||||
console.log(index)
|
||||
current.value = Number(index)
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = await getArticleCate()
|
||||
tabList.value = [{ name: '全部', id: 0 }].concat(data)
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.news {
|
||||
&-search {
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
&-list {
|
||||
height: calc(100vh - 86px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
uniapp/src/pages/news_detail/news_detail.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<view class="news-detail bg-white">
|
||||
<!-- 标题信心 -->
|
||||
<view class="news-detail-header py-[20rpx] px-[30rpx]">
|
||||
<view class="text-3xl font-medium">{{ newsData.title }}</view>
|
||||
<view class="flex mt-[20rpx] text-xs">
|
||||
<view class="mr-[40rpx]" v-if="newsData.author">作者: {{ newsData.author }}</view>
|
||||
<view class="text-muted mr-[40rpx] flex-1">{{ newsData.createTime }}</view>
|
||||
<view class="flex items-center text-muted flex-none">
|
||||
<image
|
||||
src="/static/images/icon/icon_visit.png"
|
||||
class="w-[30rpx] h-[30rpx]"
|
||||
></image>
|
||||
<view class="ml-[10rpx]">{{ newsData.visit }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 咨询内容 -->
|
||||
<view class="news-detail-section bg-white p-[24rpx]">
|
||||
<!-- 摘要 -->
|
||||
<view class="summary p-[20rpx] text-base" v-if="newsData.summary">
|
||||
<text class="font-medium">摘要: </text> {{ newsData.summary }}
|
||||
</view>
|
||||
<!-- 内容 -->
|
||||
<view class="mt-[20rpx]">
|
||||
<u-parse :html="newsData.content"></u-parse>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-btn flex items-center px-[34rpx]" @click="handleAddCollect(newsData.id)">
|
||||
<u-icon
|
||||
:name="newsData.collect ? 'star-fill' : 'star'"
|
||||
size="40"
|
||||
:color="newsData.collect ? '#F7BA47' : '#333'"
|
||||
></u-icon>
|
||||
<text class="ml-[10rpx]">收藏</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getArticleDetail, addCollect, cancelCollect } from '@/api/news'
|
||||
|
||||
const newsData = ref<any>({})
|
||||
let newsId = ''
|
||||
|
||||
const getData = async (id) => {
|
||||
newsData.value = await getArticleDetail({ id })
|
||||
}
|
||||
|
||||
const handleAddCollect = async (articleId: number) => {
|
||||
try {
|
||||
if (newsData.value.collect) {
|
||||
await cancelCollect({ articleId })
|
||||
uni.$u.toast('已取消收藏')
|
||||
} else {
|
||||
await addCollect({ articleId })
|
||||
uni.$u.toast('收藏成功')
|
||||
}
|
||||
getData(newsId)
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
newsId = options.id
|
||||
getData(newsId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.news-detail {
|
||||
height: 100%;
|
||||
|
||||
&-header {
|
||||
border-bottom: 2rpx solid #f8f8f8;
|
||||
}
|
||||
|
||||
&-section {
|
||||
.summary {
|
||||
border-radius: 12rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-btn {
|
||||
position: fixed;
|
||||
right: 30rpx;
|
||||
height: 80rpx;
|
||||
bottom: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
uniapp/src/pages/register/register.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<view
|
||||
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border"
|
||||
>
|
||||
<view class="w-full">
|
||||
<u-form borderBottom :label-width="150">
|
||||
<u-form-item label="创建账号" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.username"
|
||||
:border="false"
|
||||
placeholder="请输入账号"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="设置密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password"
|
||||
placeholder="6-20位数字+字母或符号组合"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item label="确认密码" borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
type="password"
|
||||
v-model="formData.password2"
|
||||
placeholder="请确认密码"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<view class="mt-[40rpx]" v-if="isOpenAgreement">
|
||||
<u-checkbox v-model="isCheckAgreement" shape="circle">
|
||||
<view class="text-xs flex">
|
||||
已阅读并同意
|
||||
<navigator
|
||||
@click.stop=""
|
||||
class="text-primary"
|
||||
hover-class="none"
|
||||
url="/pages/agreement/agreement?type=service"
|
||||
>
|
||||
《服务协议》
|
||||
</navigator>
|
||||
和<navigator
|
||||
@click.stop=""
|
||||
class="text-primary"
|
||||
hover-class="none"
|
||||
url="/pages/agreement/agreement?type=privacy"
|
||||
>
|
||||
《隐私协议》
|
||||
</navigator>
|
||||
</view>
|
||||
</u-checkbox>
|
||||
</view>
|
||||
<view class="mt-[60rpx]">
|
||||
<u-button type="primary" shape="circle" @click="accountRegister"> 注册 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { register } from '@/api/account'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
const isCheckAgreement = ref(false)
|
||||
const appStore = useAppStore()
|
||||
const isOpenAgreement = computed(() => appStore.getLoginConfig.openAgreement == 1)
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
})
|
||||
|
||||
const accountRegister = async () => {
|
||||
if (!isCheckAgreement.value && isOpenAgreement.value)
|
||||
return uni.$u.toast('请勾选已阅读并同意《服务协议》和《隐私协议》')
|
||||
if (!formData.username) return uni.$u.toast('请输入账号')
|
||||
if (!formData.password) return uni.$u.toast('请输入密码')
|
||||
if (!formData.password2) return uni.$u.toast('请输入确认密码')
|
||||
if (formData.password != formData.password2) return uni.$u.toast('两次输入的密码不一致')
|
||||
await register(formData)
|
||||
uni.$u.toast('注册成功')
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
77
uniapp/src/pages/search/component/suggest.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<view class="suggest bg-white">
|
||||
<!-- 热门搜索 -->
|
||||
<view class="hot" v-if="searchData.length">
|
||||
<view class="font-medium pl-[24rpx] pt-[26rpx] pb-[6rpx] text-lg">热门搜索</view>
|
||||
|
||||
<view class="w-full px-[24rpx]">
|
||||
<block v-for="(hotItem, index) in searchData" :key="index">
|
||||
<view class="keyword truncate max-w-full" @click="handleHistoreSearch(hotItem)">
|
||||
{{ hotItem }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="mx-[24rpx] my-[40rpx] border-b border-solid border-0 border-light"
|
||||
v-if="searchData.length && his_search.length"
|
||||
></view>
|
||||
|
||||
<!-- 历史搜索 -->
|
||||
<view class="history" v-if="his_search.length">
|
||||
<view class="flex justify-between px-[24rpx] pb-[6rpx] pt-[26rpx]">
|
||||
<view class="text-lg font-medium">历史搜索</view>
|
||||
<view class="text-xs text-muted" @click="() => emit('clear')">清空</view>
|
||||
</view>
|
||||
|
||||
<view class="w-full px-[24rpx]">
|
||||
<block v-for="(hisItem, index) in his_search" :key="index">
|
||||
<view class="keyword truncate" @click="handleHistoreSearch(hisItem)">{{
|
||||
hisItem
|
||||
}}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'search', value: string): void
|
||||
(event: 'clear', value: void): void
|
||||
}>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
hot_search?: string[]
|
||||
his_search?: string[]
|
||||
}>(),
|
||||
{
|
||||
hot_search: () => [],
|
||||
his_search: () => []
|
||||
}
|
||||
)
|
||||
const searchData = computed(() => {
|
||||
return props.hot_search.filter((item) => item)
|
||||
})
|
||||
|
||||
const handleHistoreSearch = (text: string) => {
|
||||
emit('search', text)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.suggest {
|
||||
height: 100%;
|
||||
.keyword {
|
||||
display: inline-block;
|
||||
margin: 24rpx 16rpx 0 0;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 26rpx;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
uniapp/src/pages/search/search.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view class="search">
|
||||
<!-- 搜索框 -->
|
||||
<view class="px-[24rpx] py-[14rpx] bg-white">
|
||||
<u-search
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词搜索"
|
||||
height="72"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
@clear="search.searching = false"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<view class="search-content">
|
||||
<!-- -->
|
||||
<suggest
|
||||
v-show="!search.searching"
|
||||
@search="handleSearch"
|
||||
@clear="handleClear"
|
||||
:hot_search="search.hot_search"
|
||||
:his_search="search.his_search"
|
||||
></suggest>
|
||||
|
||||
<!-- -->
|
||||
<view class="search-content-s pt-[20rpx]" v-show="search.searching">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
v-model="search.result"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<block v-for="(item, index) in search.result" :key="item.id">
|
||||
<news-card :item="item" :newsId="item.id"></news-card>
|
||||
</block>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, shallowRef } from 'vue'
|
||||
import Suggest from './component/suggest.vue'
|
||||
import { HISTORY } from '@/enums/cacheEnums'
|
||||
import { getHotSearch, getSearch } from '@/api/shop'
|
||||
import cache from '@/utils/cache'
|
||||
|
||||
interface Search {
|
||||
hot_search: string[]
|
||||
his_search: string[]
|
||||
result: any
|
||||
searching: boolean
|
||||
}
|
||||
|
||||
const search = reactive<Search>({
|
||||
hot_search: [],
|
||||
his_search: [],
|
||||
result: [],
|
||||
searching: false
|
||||
})
|
||||
const keyword = ref<string>('')
|
||||
const paging = shallowRef()
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
keyword.value = value
|
||||
if (keyword.value) {
|
||||
if (!search.his_search.includes(keyword.value)) {
|
||||
search.his_search.unshift(keyword.value)
|
||||
cache.set(HISTORY, search.his_search)
|
||||
}
|
||||
}
|
||||
paging.value.reload()
|
||||
search.searching = true
|
||||
}
|
||||
|
||||
const getHotSearchFunc = async () => {
|
||||
try {
|
||||
search.hot_search = await getHotSearch()
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
console.log('获取热门搜索失败=>', e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClear = async (): Promise<void> => {
|
||||
const resModel: any = await uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: '是否清空历史记录?'
|
||||
})
|
||||
if (resModel.confirm) {
|
||||
cache.set(HISTORY, '')
|
||||
search.his_search = []
|
||||
}
|
||||
}
|
||||
|
||||
const queryList = async (pageNo, pageSize) => {
|
||||
try {
|
||||
const { lists } = await getSearch({
|
||||
keyword: keyword.value,
|
||||
pageNo,
|
||||
pageSize
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
getHotSearchFunc()
|
||||
search.his_search = cache.get(HISTORY) || []
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
&-content {
|
||||
height: calc(100vh - 46px - env(safe-area-inset-bottom));
|
||||
&-s {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
46
uniapp/src/pages/user/user.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view class="user">
|
||||
<view v-for="(item, index) in state.pages" :key="index">
|
||||
<template v-if="item.name == 'user-info'">
|
||||
<w-user-info
|
||||
:content="item.content"
|
||||
:styles="item.styles"
|
||||
:user="userInfo"
|
||||
:is-login="isLogin"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="item.name == 'my-service'">
|
||||
<w-my-service :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'user-banner'">
|
||||
<w-user-banner :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
</view>
|
||||
<tabbar />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getDecorate } from '@/api/shop'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
const state = reactive<{
|
||||
pages: any[]
|
||||
}>({
|
||||
pages: []
|
||||
})
|
||||
const getData = async () => {
|
||||
const data = await getDecorate({ id: 2 })
|
||||
state.pages = JSON.parse(data.pages)
|
||||
}
|
||||
const userStore = useUserStore()
|
||||
const { userInfo, isLogin } = storeToRefs(userStore)
|
||||
onShow(() => {
|
||||
userStore.getUser()
|
||||
})
|
||||
getData()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
360
uniapp/src/pages/user_data/user_data.vue
Normal file
@@ -0,0 +1,360 @@
|
||||
<template>
|
||||
<!-- Main Start -->
|
||||
<!-- 头部修改头像 -->
|
||||
<view class="header bg-white pt-[30rpx]">
|
||||
<view class="flex justify-center">
|
||||
<image @click="uploaderAvatar" :src="userInfo?.avatar"></image>
|
||||
</view>
|
||||
<view class="mt-[20rpx] text-center text-muted text-xs" @click="uploaderAvatar"
|
||||
>点击修改头像</view
|
||||
>
|
||||
</view>
|
||||
|
||||
<!-- 用户ID -->
|
||||
<view
|
||||
class="item text-nr flex justify-between"
|
||||
@click=";(showUserName = true), (newUsername = userInfo?.username)"
|
||||
>
|
||||
<view class="label">账号</view>
|
||||
<view class="content">{{ userInfo?.username }}</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 昵称 -->
|
||||
<view
|
||||
class="item text-nr flex justify-between"
|
||||
@click=";(showNickName = true), (newNickname = userInfo?.nickname)"
|
||||
>
|
||||
<view class="label">昵称</view>
|
||||
<view class="content">{{ userInfo?.nickname }}</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 性别 -->
|
||||
<view class="item text-nr flex justify-between" @click="changeSex">
|
||||
<view class="label">性别</view>
|
||||
<view class="content">{{ userInfo?.sex }}</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<view class="item text-nr flex justify-between">
|
||||
<view class="label">手机号</view>
|
||||
<view class="content">{{
|
||||
userInfo?.mobile == '' ? '未绑定手机号' : userInfo?.mobile
|
||||
}}</view>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<u-button
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
type="primary"
|
||||
shape="circle"
|
||||
size="mini"
|
||||
:plain="true"
|
||||
>
|
||||
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
|
||||
</u-button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<u-button
|
||||
@click="showMobilePop = true"
|
||||
size="mini"
|
||||
type="primary"
|
||||
shape="circle"
|
||||
:plain="true"
|
||||
>
|
||||
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
|
||||
</u-button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 注册时间 -->
|
||||
<view class="item text-nr flex justify-between">
|
||||
<view class="label">注册时间</view>
|
||||
<view class="content">{{ userInfo?.createTime }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称修改组件 -->
|
||||
<u-popup v-model="showNickName" :closeable="true" mode="center" border-radius="20">
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改昵称</view>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="newNickname"
|
||||
placeholder="请输入昵称"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<u-button @click="changeNameConfirm" type="primary" shape="circle"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 账号修改组件 -->
|
||||
<u-popup v-model="showUserName" :closeable="true" mode="center" border-radius="20">
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改账号</view>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="newUsername"
|
||||
placeholder="请输入账号"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<u-button @click="changeUserNameConfirm" type="primary" shape="circle">
|
||||
确定
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 性别修改组件 -->
|
||||
<u-picker
|
||||
mode="selector"
|
||||
v-model="showPicker"
|
||||
confirm-color="#4173FF"
|
||||
:default-selector="[0]"
|
||||
:range="sexList"
|
||||
@confirm="changeSexConfirm"
|
||||
/>
|
||||
|
||||
<!-- 账号修改组件 -->
|
||||
<u-popup v-model="showMobilePop" :closeable="true" mode="center" border-radius="20">
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改手机号码</view>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="newMobile"
|
||||
placeholder="请输入新的手机号码"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="mobileCode"
|
||||
placeholder="请输入验证码"
|
||||
:border="false"
|
||||
/>
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
|
||||
@click="sendSms"
|
||||
>
|
||||
<u-verification-code
|
||||
ref="uCodeRef"
|
||||
:seconds="60"
|
||||
@change="codeChange"
|
||||
change-text="x秒"
|
||||
/>
|
||||
{{ codeTips }}
|
||||
</view>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<u-button @click="changeCodeMobile" type="primary" shape="circle"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { getUserInfo, userEdit, userBindMobile, userMnpMobile } from '@/api/user'
|
||||
import { smsSend } from '@/api/app'
|
||||
import { FieldType, SMSEnum } from '@/enums/appEnums'
|
||||
import { uploadFile } from '@/utils/util'
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<any | null>(null)
|
||||
// 用户信息的枚举
|
||||
const fieldType = ref(FieldType.NONE)
|
||||
//选择性别数据
|
||||
const sexList = ref<Array<string> | null>(['男', '女'])
|
||||
|
||||
//显示昵称弹窗
|
||||
const showNickName = ref<boolean | null>(false)
|
||||
//显示账户弹窗
|
||||
const showUserName = ref<boolean | null>(false)
|
||||
//显示性别选择弹窗
|
||||
const showPicker = ref<boolean | null>(false)
|
||||
// 显示手机号验证码调整弹窗 非小程序才需要
|
||||
const showMobilePop = ref<boolean | null>(false)
|
||||
|
||||
//新昵称
|
||||
const newNickname = ref<string>('')
|
||||
//新账号
|
||||
const newUsername = ref<string>('')
|
||||
//新的手机号码
|
||||
const newMobile = ref<string>('')
|
||||
|
||||
//修改手机验证码
|
||||
const mobileCode = ref<string>('')
|
||||
const codeTips = ref('')
|
||||
const uCodeRef = shallowRef()
|
||||
|
||||
// 获取用户信息
|
||||
const getUser = async (): Promise<void> => {
|
||||
userInfo.value = await getUserInfo()
|
||||
}
|
||||
|
||||
// 获取验证码显示字段
|
||||
const codeChange = (text: string) => {
|
||||
codeTips.value = text
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
const sendSms = async () => {
|
||||
if (!newMobile.value) return uni.$u.toast('请输入新的手机号码')
|
||||
if (uCodeRef.value?.canGetCode) {
|
||||
await smsSend({
|
||||
scene: userInfo.value.mobile ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
|
||||
mobile: newMobile.value
|
||||
})
|
||||
uni.$u.toast('发送成功')
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码修改手机号-非微信小程序
|
||||
const changeCodeMobile = async () => {
|
||||
await userBindMobile({
|
||||
type: userInfo.value.mobile ? 'change' : 'bind',
|
||||
mobile: newMobile.value,
|
||||
code: mobileCode.value
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
showMobilePop.value = false
|
||||
getUser()
|
||||
}
|
||||
|
||||
// 修改用户信息
|
||||
const setUserInfoFun = async (value: string): Promise<void> => {
|
||||
await userEdit({
|
||||
field: fieldType.value,
|
||||
value: value
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
getUser()
|
||||
}
|
||||
|
||||
// 上传头像
|
||||
const uploaderAvatar = () => {
|
||||
fieldType.value = FieldType.AVATAR
|
||||
uni.navigateTo({
|
||||
url: '/uni_modules/vk-uview-ui/components/u-avatar-cropper/u-avatar-cropper?destWidth=300&rectWidth=200&fileType=jpg'
|
||||
})
|
||||
}
|
||||
|
||||
// 显示修改用户性别弹窗
|
||||
const changeSex = () => {
|
||||
showPicker.value = true
|
||||
fieldType.value = FieldType.SEX
|
||||
}
|
||||
|
||||
// 修改用户性别
|
||||
const changeSexConfirm = (value) => {
|
||||
setUserInfoFun(value[0] + 1)
|
||||
showPicker.value = false
|
||||
}
|
||||
|
||||
// 修改用户账号
|
||||
const changeUserNameConfirm = () => {
|
||||
if (newUsername.value == '') return uni.$u.toast('账号不能为空')
|
||||
if (newUsername.value.length > 10) return uni.$u.toast('账号长度不得超过十位数')
|
||||
|
||||
fieldType.value = FieldType.USERNAME
|
||||
setUserInfoFun(newUsername.value)
|
||||
showUserName.value = false
|
||||
}
|
||||
|
||||
// 修改用户昵称
|
||||
const changeNameConfirm = () => {
|
||||
if (newNickname.value == '') return uni.$u.toast('昵称不能为空')
|
||||
if (newNickname.value.length > 10) return uni.$u.toast('昵称长度不得超过十位数')
|
||||
showNickName.value = false
|
||||
fieldType.value = FieldType.NICKNAME
|
||||
setUserInfoFun(newNickname.value)
|
||||
}
|
||||
|
||||
// 微信小程序 绑定||修改用户手机号
|
||||
const getPhoneNumber = async (e): Promise<void> => {
|
||||
const { encryptedData, iv, code } = e.detail
|
||||
const data = {
|
||||
code,
|
||||
encrypted_data: encryptedData,
|
||||
iv
|
||||
}
|
||||
if (encryptedData) {
|
||||
await userMnpMobile({
|
||||
...data
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
getUser()
|
||||
}
|
||||
}
|
||||
|
||||
const goPage = (url: string) => {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
|
||||
// 监听从裁剪页发布的事件,获得裁剪结果
|
||||
uni.$on('uAvatarCropper', (path) => {
|
||||
uni.showLoading({
|
||||
title: '正在上传中...',
|
||||
mask: true
|
||||
})
|
||||
uploadFile(path)
|
||||
.then((res) => {
|
||||
uni.hideLoading()
|
||||
setUserInfoFun(res.url)
|
||||
})
|
||||
.catch(() => {
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('上传失败')
|
||||
})
|
||||
})
|
||||
|
||||
onShow(async () => {
|
||||
getUser()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off('uAvatarCropper')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 2rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #ffffff;
|
||||
|
||||
.label {
|
||||
width: 150rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
uniapp/src/pages/user_set/user_set.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="user-set">
|
||||
<navigator :url="`/pages/user_data/user_data`">
|
||||
<view class="item flex bg-white mt-[20rpx]">
|
||||
<u-avatar :src="userInfo.avatar" shape="square" :size="100"></u-avatar>
|
||||
<view class="ml-[20rpx] flex flex-1 justify-between items-center">
|
||||
<view>
|
||||
<view class="mb-[15rpx] text-xl font-medium">{{ userInfo.nickname }}</view>
|
||||
<view class="text-content text-xs">账号:{{ userInfo.username }}</view>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#666"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<view
|
||||
class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between"
|
||||
@click="handlePwd"
|
||||
>
|
||||
<view class="">登录密码</view>
|
||||
<u-icon name="arrow-right" color="#666"></u-icon>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN || H5 -->
|
||||
<view class="item bg-white flex flex-1 justify-between" v-if="isWeixin">
|
||||
<view class="">绑定微信</view>
|
||||
<view class="flex justify-between">
|
||||
<view class="text-muted mr-[20rpx]">
|
||||
{{ userInfo.isBindMnp ? '已绑定' : '未绑定' }}
|
||||
</view>
|
||||
<!-- <u-icon name="arrow-right" color="#666"></u-icon> -->
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.PRIVACY}`">
|
||||
<view class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between">
|
||||
<view class="">隐私政策</view>
|
||||
<u-icon name="arrow-right" color="#666"></u-icon>
|
||||
</view>
|
||||
</navigator>
|
||||
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.SERVICE}`">
|
||||
<view class="item bg-white btn-border flex flex-1 justify-between">
|
||||
<view class="">服务协议</view>
|
||||
<u-icon name="arrow-right" color="#666"></u-icon>
|
||||
</view>
|
||||
</navigator>
|
||||
<navigator url="/pages/as_us/as_us">
|
||||
<view class="item bg-white flex flex-1 justify-between">
|
||||
<view class="">关于我们</view>
|
||||
<view class="flex justify-between">
|
||||
<view class="text-muted mr-[20rpx]">
|
||||
{{ appStore.config.version }}
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#666"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
|
||||
<view class="mt-[60rpx] mx-[26rpx]">
|
||||
<u-button type="primary" shape="circle" @click="logoutHandle"> 退出登录 </u-button>
|
||||
</view>
|
||||
|
||||
<u-action-sheet
|
||||
:list="list"
|
||||
v-model="show"
|
||||
@click="handleClick"
|
||||
:safe-area-inset-bottom="true"
|
||||
></u-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getUserInfo } from '@/api/user'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { AgreementEnum } from '@/enums/agreementEnums'
|
||||
import { isWeixinClient } from '@/utils/client'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const userInfo = ref({
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
username: '',
|
||||
isBindMnp: '',
|
||||
isPassword: ''
|
||||
})
|
||||
const list = ref([
|
||||
{
|
||||
text: '修改密码'
|
||||
},
|
||||
{
|
||||
text: '忘记密码'
|
||||
}
|
||||
])
|
||||
|
||||
const isWeixin = ref(true)
|
||||
// #ifdef H5
|
||||
isWeixin.value = isWeixinClient()
|
||||
// #endif
|
||||
|
||||
const show = ref(false)
|
||||
|
||||
// 获取用户信息
|
||||
const getUser = async () => {
|
||||
const res = await getUserInfo()
|
||||
userInfo.value = res
|
||||
}
|
||||
|
||||
// 修改/忘记密码
|
||||
const handleClick = (index: number) => {
|
||||
switch (index) {
|
||||
case 0:
|
||||
uni.navigateTo({ url: '/pages/change_password/change_password' })
|
||||
break
|
||||
case 1:
|
||||
uni.navigateTo({ url: '/pages/forget_pwd/forget_pwd' })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handlePwd = () => {
|
||||
if (!userInfo.value.isPassword)
|
||||
return uni.navigateTo({ url: '/pages/change_password/change_password?type=set' })
|
||||
show.value = true
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logoutHandle = () => {
|
||||
uni.showModal({
|
||||
content: '是否退出登录?',
|
||||
confirmColor: '#4173FF',
|
||||
success: ({ cancel }) => {
|
||||
if (cancel) return
|
||||
userStore.logout()
|
||||
uni.redirectTo({ url: '/pages/login/login' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getUser()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-set {
|
||||
.item {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.btn-border {
|
||||
border-bottom: 2rpx solid #f8f8f8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
uniapp/src/pages/webview/webview.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<web-view :src="url" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const url = ref('')
|
||||
|
||||
onLoad((options) => {
|
||||
url.value = decodeURIComponent(options.url!)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
12
uniapp/src/plugins/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { App } from 'vue'
|
||||
const modules = import.meta.globEager('./modules/**/*.ts')
|
||||
|
||||
export default {
|
||||
install: (app: App) => {
|
||||
for (const module of Object.values(modules)) {
|
||||
const fun = module.default
|
||||
isFunction(fun) && fun(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
6
uniapp/src/plugins/modules/pinia.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
const pinia = createPinia()
|
||||
export default (app: App) => {
|
||||
app.use(pinia)
|
||||
}
|
||||
7
uniapp/src/plugins/modules/uview.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { App } from 'vue'
|
||||
import uView from '@/uni_modules/vk-uview-ui'
|
||||
|
||||
export default (app: App) => {
|
||||
// 使用 uView UI
|
||||
app.use(uView)
|
||||
}
|
||||
14
uniapp/src/plugins/modules/vconsole.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// #ifdef H5
|
||||
// 提交前需要注释 本地调试使用
|
||||
import Vconsole from 'vconsole'
|
||||
import { isDevMode } from '@/utils/env'
|
||||
// #endif
|
||||
|
||||
export default () => {
|
||||
// #ifdef H5
|
||||
if (isDevMode()) {
|
||||
const vConsole = new Vconsole()
|
||||
return vConsole
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
46
uniapp/src/router/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { BACK_URL } from '@/enums/cacheEnums'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import cache from '@/utils/cache'
|
||||
import { routes } from './routes'
|
||||
|
||||
const whiteList = ['register', 'login', 'forget_pwd']
|
||||
const list = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']
|
||||
list.forEach((item) => {
|
||||
uni.addInterceptor(item, {
|
||||
invoke(e) {
|
||||
// 获取要跳转的页面路径(url去掉"?"和"?"后的参数)
|
||||
const url = e.url.split('?')[0]
|
||||
const currentRoute = routes.find((item) => {
|
||||
return url === item.path
|
||||
})
|
||||
// 需要登录并且没有token
|
||||
if (currentRoute?.auth && !getToken()) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
return false
|
||||
}
|
||||
return e
|
||||
},
|
||||
fail(err) {
|
||||
// 失败回调拦截
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export function setupRouter() {
|
||||
// #ifdef H5
|
||||
const app = getApp()
|
||||
app.$router.afterEach((to: any, from: any) => {
|
||||
const index = whiteList.findIndex((item) => from.path.includes(item) || from.path === '/')
|
||||
const userStore = useUserStore()
|
||||
if (index == -1 && !userStore.isLogin) {
|
||||
//保存登录前的路径
|
||||
cache.set(BACK_URL, from.fullPath)
|
||||
}
|
||||
})
|
||||
|
||||
// #endif
|
||||
}
|
||||
47
uniapp/src/router/routes.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import PagesJSON from '../pages.json'
|
||||
const CONFIG = {
|
||||
includes: ['path', 'aliasPath', 'name', 'auth']
|
||||
}
|
||||
|
||||
function getPagesRoutes(pages: any[], rootPath = null) {
|
||||
const routes: any[] = []
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const item = pages[i]
|
||||
const route: any = {}
|
||||
for (let j = 0; j < CONFIG.includes.length; j++) {
|
||||
const key = CONFIG.includes[j]
|
||||
let value = item[key]
|
||||
if (key === 'path') {
|
||||
value = rootPath ? `/${rootPath}/${value}` : `/${value}`
|
||||
}
|
||||
if (key === 'aliasPath' && i == 0 && rootPath == null) {
|
||||
route[key] = route[key] || '/'
|
||||
} else if (value !== undefined) {
|
||||
route[key] = value
|
||||
}
|
||||
}
|
||||
routes.push(route)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
function getSubPackagesRoutes(pagesJson: any) {
|
||||
const { subPackages } = pagesJson
|
||||
let routes: any[] = []
|
||||
if (subPackages == null || subPackages.length == 0) {
|
||||
return []
|
||||
}
|
||||
for (let i = 0; i < subPackages.length; i++) {
|
||||
const subPages = subPackages[i].pages
|
||||
const root = subPackages[i].root
|
||||
const subRoutes = getPagesRoutes(subPages, root)
|
||||
routes = routes.concat(subRoutes)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
export function generateRoutes() {
|
||||
return getPagesRoutes(PagesJSON.pages).concat(getSubPackagesRoutes(PagesJSON))
|
||||
}
|
||||
|
||||
export const routes = generateRoutes()
|
||||
BIN
uniapp/src/static/images/icon/icon_code.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
uniapp/src/static/images/icon/icon_mobile.png
Normal file
|
After Width: | Height: | Size: 679 B |
BIN
uniapp/src/static/images/icon/icon_password.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
uniapp/src/static/images/icon/icon_user.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
uniapp/src/static/images/icon/icon_visit.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
uniapp/src/static/images/icon/icon_wx.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
uniapp/src/static/images/tabs/home.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
uniapp/src/static/images/tabs/home_s.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
uniapp/src/static/images/tabs/news.png
Normal file
|
After Width: | Height: | Size: 831 B |
BIN
uniapp/src/static/images/tabs/news_s.png
Normal file
|
After Width: | Height: | Size: 839 B |
BIN
uniapp/src/static/images/tabs/user.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
uniapp/src/static/images/tabs/user_s.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
uniapp/src/static/images/user/default_avatar.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
uniapp/src/static/images/user/my_topbg.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
28
uniapp/src/stores/app.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getConfig } from '@/api/app'
|
||||
|
||||
interface AppSate {
|
||||
config: Record<string, any>
|
||||
}
|
||||
export const useAppStore = defineStore({
|
||||
id: 'appStore',
|
||||
state: (): AppSate => ({
|
||||
config: {}
|
||||
}),
|
||||
getters: {
|
||||
getWebsiteConfig: (state) => state.config.website || {},
|
||||
getLoginConfig: (state) => state.config.login || {},
|
||||
getTabbarConfig: (state) => state.config.tabbar || [],
|
||||
getStyleConfig: (state) => state.config.style || {},
|
||||
getH5Config: (state) => state.config.h5 || {}
|
||||
},
|
||||
actions: {
|
||||
getImageUrl(url: string) {
|
||||
return url ? `${this.config.domain}${url}` : ''
|
||||
},
|
||||
async getConfig() {
|
||||
const data = await getConfig()
|
||||
this.config = data
|
||||
}
|
||||
}
|
||||
})
|
||||
38
uniapp/src/stores/user.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { getUserCenter } from '@/api/user'
|
||||
import { TOKEN_KEY } from '@/enums/cacheEnums'
|
||||
import cache from '@/utils/cache'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
interface UserSate {
|
||||
userInfo: Record<string, any>
|
||||
token: string | null
|
||||
temToken: string | null
|
||||
}
|
||||
export const useUserStore = defineStore({
|
||||
id: 'userStore',
|
||||
state: (): UserSate => ({
|
||||
userInfo: {},
|
||||
token: cache.get(TOKEN_KEY) || null,
|
||||
temToken: null
|
||||
}),
|
||||
getters: {
|
||||
isLogin: (state) => !!state.token
|
||||
},
|
||||
actions: {
|
||||
async getUser() {
|
||||
const data = await getUserCenter({
|
||||
token: this.token || this.temToken
|
||||
})
|
||||
this.userInfo = data
|
||||
},
|
||||
login(token: string) {
|
||||
this.token = token
|
||||
cache.set(TOKEN_KEY, token)
|
||||
},
|
||||
logout() {
|
||||
this.token = ''
|
||||
this.userInfo = {}
|
||||
cache.remove(TOKEN_KEY)
|
||||
}
|
||||
}
|
||||
})
|
||||
3
uniapp/src/styles/index.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import './tailwind.css';
|
||||
@import './public.scss';
|
||||
@import '../uni_modules/vk-uview-ui/index.scss';
|
||||
4
uniapp/src/styles/public.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
page {
|
||||
background-color: $u-bg-color;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
5
uniapp/src/styles/tailwind.css
Normal file
@@ -0,0 +1,5 @@
|
||||
/* #ifdef H5 */
|
||||
@tailwind base;
|
||||
/* #endif */
|
||||
|
||||
@tailwind utilities;
|
||||
0
uniapp/src/styles/var.css
Normal file
37
uniapp/src/uni.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
@import '@/uni_modules/vk-uview-ui/theme.scss';
|
||||
|
||||
$u-main-color: #333333;
|
||||
$u-content-color: #666666;
|
||||
$u-tips-color: #999999;
|
||||
$u-light-color: #c0c4cc;
|
||||
$u-border-color: #e5e5e5;
|
||||
$u-bg-color: #f3f4f6;
|
||||
$u-disabled-color: #c8c9cc;
|
||||
|
||||
$u-type-primary: #4173ff;
|
||||
$u-type-primary-light: #ecf5ff;
|
||||
$u-type-primary-disabled: #a0cfff;
|
||||
$u-type-primary-dark: #2b85e4;
|
||||
|
||||
$u-type-warning: #ff9900;
|
||||
$u-type-warning-disabled: #fcbd71;
|
||||
$u-type-warning-dark: #f29100;
|
||||
$u-type-warning-light: #fdf6ec;
|
||||
|
||||
$u-type-success: #19be6b;
|
||||
$u-type-success-disabled: #71d5a1;
|
||||
$u-type-success-dark: #18b566;
|
||||
$u-type-success-light: #dbf1e1;
|
||||
|
||||
$u-type-error: #fa3534;
|
||||
$u-type-error-disabled: #fab6b6;
|
||||
$u-type-error-dark: #dd6161;
|
||||
$u-type-error-light: #fef0f0;
|
||||
|
||||
$u-type-info: #909399;
|
||||
$u-type-info-disabled: #c8c9cc;
|
||||
$u-type-info-dark: #82848a;
|
||||
$u-type-info-light: #f4f4f5;
|
||||
|
||||
$u-form-item-height: 60rpx;
|
||||
$u-form-item-border-color: #e5e5e5;
|
||||
118
uniapp/src/uni_modules/vk-uview-ui/changelog.md
Normal file
@@ -0,0 +1,118 @@
|
||||
## 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(不模糊)
|
||||
* 
|
||||
## 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
|
||||
@@ -0,0 +1,246 @@
|
||||
<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>
|
||||
@@ -0,0 +1,257 @@
|
||||
<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>
|
||||
@@ -0,0 +1,290 @@
|
||||
<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>
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<view class="u-avatar" :style="[wrapStyle]" @tap="click">
|
||||
<image
|
||||
@error="loadError"
|
||||
:style="[imgStyle]"
|
||||
class="u-avatar__img"
|
||||
v-if="!uText && avatar"
|
||||
:src="avatar"
|
||||
:mode="imgMode"
|
||||
></image>
|
||||
<text
|
||||
class="u-line-1"
|
||||
v-else-if="uText"
|
||||
:style="{
|
||||
fontSize: '38rpx'
|
||||
}"
|
||||
>
|
||||
{{ uText }}
|
||||
</text>
|
||||
<slot v-else></slot>
|
||||
<view
|
||||
class="u-avatar__sex"
|
||||
v-if="showSex"
|
||||
:class="['u-avatar__sex--' + sexIcon]"
|
||||
:style="[uSexStyle]"
|
||||
>
|
||||
<u-icon :name="sexIcon" size="20"></u-icon>
|
||||
</view>
|
||||
<view class="u-avatar__level" v-if="showLevel" :style="[uLevelStyle]">
|
||||
<u-icon :name="levelIcon" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let base64Avatar =
|
||||
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
|
||||
/**
|
||||
* avatar 头像
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/avatar.html
|
||||
* @property {String} bg-color 背景颜色,一般显示文字时用(默认#ffffff)
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像
|
||||
* @property {String Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值,单位rpx(默认default)
|
||||
* @property {String} mode 显示类型,见上方说明(默认circle)
|
||||
* @property {String} sex-icon 性别图标,man-男,woman-女(默认man)
|
||||
* @property {String} level-icon 等级图标(默认level)
|
||||
* @property {String} sex-bg-color 性别图标背景颜色
|
||||
* @property {String} level-bg-color 等级图标背景颜色
|
||||
* @property {String} show-sex 是否显示性别图标(默认false)
|
||||
* @property {String} show-level 是否显示等级图标(默认false)
|
||||
* @property {String} img-mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值(默认aspectFill)
|
||||
* @property {String} index 用户传递的标识符值,如果是列表循环,可穿v-for的index值
|
||||
* @event {Function} click 头像被点击
|
||||
* @example <u-avatar :src="src"></u-avatar>
|
||||
*/
|
||||
export default {
|
||||
name: "u-avatar",
|
||||
emits: ["click"],
|
||||
props: {
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: "transparent"
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 尺寸,large-大,default-中等,mini-小,如果为数值,则单位为rpx
|
||||
// 宽度等于高度
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: "default"
|
||||
},
|
||||
// 头像模型,square-带圆角方形,circle-圆形
|
||||
mode: {
|
||||
type: String,
|
||||
default: "circle"
|
||||
},
|
||||
// 文字内容
|
||||
text: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 图片的裁剪模型
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: "aspectFill"
|
||||
},
|
||||
// 标识符
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
// 右上角性别角标,man-男,woman-女
|
||||
sexIcon: {
|
||||
type: String,
|
||||
default: "man"
|
||||
},
|
||||
// 右下角的等级图标
|
||||
levelIcon: {
|
||||
type: String,
|
||||
default: "level"
|
||||
},
|
||||
// 右下角等级图标背景颜色
|
||||
levelBgColor: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 右上角性别图标的背景颜色
|
||||
sexBgColor: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 是否显示性别图标
|
||||
showSex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示等级图标
|
||||
showLevel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
// 头像的地址,因为如果加载错误,需要赋值为默认图片,props值无法修改,所以需要一个中间值
|
||||
avatar: this.src ? this.src : base64Avatar
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
src(n) {
|
||||
// 用户可能会在头像加载失败时,再次修改头像值,所以需要重新赋值
|
||||
if (!n) {
|
||||
// 如果传入null或者'',或者undefined,显示默认头像
|
||||
this.avatar = base64Avatar;
|
||||
this.error = true;
|
||||
} else {
|
||||
this.avatar = n;
|
||||
this.error = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
let style = {};
|
||||
style.height =
|
||||
this.size == "large"
|
||||
? "120rpx"
|
||||
: this.size == "default"
|
||||
? "90rpx"
|
||||
: this.size == "mini"
|
||||
? "70rpx"
|
||||
: this.size + "rpx";
|
||||
style.width = style.height;
|
||||
style.flex = `0 0 ${style.height}`;
|
||||
style.backgroundColor = this.bgColor;
|
||||
style.borderRadius = this.mode == "circle" ? "500px" : "5px";
|
||||
if (this.text) style.padding = `0 6rpx`;
|
||||
return style;
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {};
|
||||
style.borderRadius = this.mode == "circle" ? "500px" : "5px";
|
||||
return style;
|
||||
},
|
||||
// 取字符串的第一个字符
|
||||
uText() {
|
||||
return String(this.text)[0];
|
||||
},
|
||||
// 性别图标的自定义样式
|
||||
uSexStyle() {
|
||||
let style = {};
|
||||
if (this.sexBgColor) style.backgroundColor = this.sexBgColor;
|
||||
return style;
|
||||
},
|
||||
// 等级图标的自定义样式
|
||||
uLevelStyle() {
|
||||
let style = {};
|
||||
if (this.levelBgColor) style.backgroundColor = this.levelBgColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 图片加载错误时,显示默认头像
|
||||
loadError() {
|
||||
this.error = true;
|
||||
this.avatar = base64Avatar;
|
||||
},
|
||||
click() {
|
||||
this.$emit("click", this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__sex {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
top: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
|
||||
&--man {
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--woman {
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--none {
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&__level {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
bottom: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<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>
|
||||
@@ -0,0 +1,216 @@
|
||||
<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>
|
||||
@@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<button
|
||||
id="u-wave-btn"
|
||||
class="u-btn u-line-1 u-fix-ios-appearance"
|
||||
:class="[
|
||||
'u-size-' + size,
|
||||
plain ? 'u-btn--' + type + '--plain' : '',
|
||||
loading ? 'u-loading' : '',
|
||||
shape == 'circle' ? 'u-round-circle' : '',
|
||||
hairLine ? showHairLineBorder : 'u-btn--bold-border',
|
||||
'u-btn--' + type,
|
||||
disabled ? `u-btn--${type}--disabled` : '',
|
||||
]"
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:disabled="disabled"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
:app-parameter="appParameter"
|
||||
:hover-stop-propagation="hoverStopPropagation"
|
||||
:send-message-title="sendMessageTitle"
|
||||
send-message-path="sendMessagePath"
|
||||
:lang="lang"
|
||||
:data-name="dataName"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
@getphonenumber="getphonenumber"
|
||||
@getuserinfo="getuserinfo"
|
||||
@error="error"
|
||||
@opensetting="opensetting"
|
||||
@launchapp="launchapp"
|
||||
:style="[customStyle, {
|
||||
overflow: ripple ? 'hidden' : 'visible'
|
||||
}]"
|
||||
@tap.stop="click($event)"
|
||||
:hover-class="getHoverClass"
|
||||
:loading="loading"
|
||||
>
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="ripple"
|
||||
class="u-wave-ripple"
|
||||
:class="[waveActive ? 'u-wave-active' : '']"
|
||||
:style="{
|
||||
top: rippleTop + 'px',
|
||||
left: rippleLeft + 'px',
|
||||
width: fields.targetWidth + 'px',
|
||||
height: fields.targetWidth + 'px',
|
||||
'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)'
|
||||
}"
|
||||
></view>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://www.uviewui.com/components/button.html
|
||||
* @property {String} size 按钮的大小
|
||||
* @property {Boolean} ripple 是否开启点击水波纹效果
|
||||
* @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效
|
||||
* @property {String} type 按钮的样式类型
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {Boolean} hair-line 是否显示按钮的细边框(默认true)
|
||||
* @property {Boolean} shape 按钮外观形状,见文档说明
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈)
|
||||
* @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} open-type 开放能力
|
||||
* @property {String} data-name 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持)
|
||||
* @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒
|
||||
* @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒
|
||||
* @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明
|
||||
* @event {Function} click 按钮点击
|
||||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调
|
||||
* @event {Function} opensetting 在打开授权设置页并关闭后回调
|
||||
* @event {Function} launchapp 打开 APP 成功的回调
|
||||
* @example <u-button>月落</u-button>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-button',
|
||||
emits: ["click", "getphonenumber", "getuserinfo", "error", "opensetting", "launchapp"],
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 按钮的预置样式,default,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮尺寸,default,medium,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 手指按(触摸)按钮时按钮时的背景颜色
|
||||
hoverBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 水波纹的背景颜色
|
||||
rippleBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否开启水波纹效果
|
||||
ripple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按下的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式,对象形式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: 500
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: 150
|
||||
},
|
||||
timerId: {
|
||||
type: [String, Number]
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 当没有传bgColor变量时,按钮按下去的颜色类名
|
||||
getHoverClass() {
|
||||
// 如果开启水波纹效果,则不启用hover-class效果
|
||||
if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
|
||||
let hoverClass = '';
|
||||
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
|
||||
return hoverClass;
|
||||
},
|
||||
// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象
|
||||
showHairLineBorder() {
|
||||
if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
|
||||
return '';
|
||||
} else {
|
||||
return 'u-hairline-border';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let btnTimerId = this.timerId || "button_" + Math.floor(Math.random() * 100000000 + 0);
|
||||
return {
|
||||
btnTimerId,
|
||||
rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离
|
||||
rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离
|
||||
fields: {}, // 波纹按钮节点信息
|
||||
waveActive: false // 激活水波纹
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击
|
||||
click(e) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
this.$u.throttle(() => {
|
||||
// 如果按钮时disabled和loading状态,不触发水波纹效果
|
||||
if (this.loading === true || this.disabled === true) return;
|
||||
// 是否开启水波纹效果
|
||||
if (this.ripple) {
|
||||
// 每次点击时,移除上一次的类,再次添加,才能触发动画效果
|
||||
this.waveActive = false;
|
||||
this.$nextTick(function() {
|
||||
this.getWaveQuery(e);
|
||||
});
|
||||
}
|
||||
this.$emit('click', e);
|
||||
}, this.throttleTime, true, this.btnTimerId);
|
||||
},
|
||||
// 查询按钮的节点信息
|
||||
getWaveQuery(e) {
|
||||
this.getElQuery().then(res => {
|
||||
// 查询返回的是一个数组节点
|
||||
let data = res[0];
|
||||
// 查询不到节点信息,不操作
|
||||
if (!data.width || !data.width) return;
|
||||
// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边
|
||||
// 最终的方形(变换后的圆形)才能覆盖整个按钮
|
||||
data.targetWidth = data.height > data.width ? data.height : data.width;
|
||||
if (!data.targetWidth) return;
|
||||
this.fields = data;
|
||||
let touchesX = '',
|
||||
touchesY = '';
|
||||
// #ifdef MP-BAIDU
|
||||
touchesX = e.changedTouches[0].clientX;
|
||||
touchesY = e.changedTouches[0].clientY;
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
touchesX = e.detail.clientX;
|
||||
touchesY = e.detail.clientY;
|
||||
// #endif
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY
|
||||
touchesX = e.touches[0].clientX;
|
||||
touchesY = e.touches[0].clientY;
|
||||
// #endif
|
||||
// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top
|
||||
// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置
|
||||
// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置
|
||||
this.rippleTop = touchesY - data.top - data.targetWidth / 2;
|
||||
this.rippleLeft = touchesX - data.left - data.targetWidth / 2;
|
||||
this.$nextTick(() => {
|
||||
this.waveActive = true;
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取节点信息
|
||||
getElQuery() {
|
||||
return new Promise(resolve => {
|
||||
let queryInfo = '';
|
||||
// 获取元素节点信息,请查看uniapp相关文档
|
||||
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
|
||||
queryInfo = uni.createSelectorQuery().in(this);
|
||||
//#ifdef MP-ALIPAY
|
||||
queryInfo = uni.createSelectorQuery();
|
||||
//#endif
|
||||
queryInfo.select('.u-btn').boundingClientRect();
|
||||
queryInfo.exec(data => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接
|
||||
getphonenumber(res) {
|
||||
this.$emit('getphonenumber', res);
|
||||
},
|
||||
getuserinfo(res) {
|
||||
this.$emit('getuserinfo', res);
|
||||
},
|
||||
error(res) {
|
||||
this.$emit('error', res);
|
||||
},
|
||||
opensetting(res) {
|
||||
this.$emit('opensetting', res);
|
||||
},
|
||||
launchapp(res) {
|
||||
this.$emit('launchapp', res);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
.u-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.u-btn {
|
||||
position: relative;
|
||||
border: 0;
|
||||
//border-radius: 10rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
// 避免边框某些场景可能被“裁剪”,不能设置为hidden
|
||||
overflow: visible;
|
||||
line-height: 1;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0 40rpx;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.15s;
|
||||
|
||||
&--bold-border {
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
&--default {
|
||||
color: $u-content-color;
|
||||
border-color: #c0c4cc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-primary;
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-success;
|
||||
background-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-error;
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-warning;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
|
||||
&--default--disabled {
|
||||
color: #ffffff;
|
||||
border-color: #e4e7ed;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-disabled!important;
|
||||
}
|
||||
|
||||
&--success--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-disabled!important;
|
||||
}
|
||||
|
||||
&--error--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-disabled!important;
|
||||
}
|
||||
|
||||
&--warning--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-disabled!important;
|
||||
}
|
||||
|
||||
&--primary--plain {
|
||||
color: $u-type-primary!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-light!important;
|
||||
}
|
||||
|
||||
&--success--plain {
|
||||
color: $u-type-success!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-light!important;
|
||||
}
|
||||
|
||||
&--error--plain {
|
||||
color: $u-type-error!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-light!important;
|
||||
}
|
||||
|
||||
&--warning--plain {
|
||||
color: $u-type-warning!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-light!important;
|
||||
}
|
||||
}
|
||||
|
||||
.u-hairline-border:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border)
|
||||
box-sizing: border-box;
|
||||
// 中心点作为变形(scale())的原点
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 199.8%;
|
||||
height: 199.7%;
|
||||
-webkit-transform: scale(0.5, 0.5);
|
||||
transform: scale(0.5, 0.5);
|
||||
border: 1px solid currentColor;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.u-wave-ripple {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.u-wave-ripple.u-wave-active {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
transition: opacity 1s linear, transform 0.4s linear;
|
||||
}
|
||||
|
||||
.u-round-circle {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-round-circle::after {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-loading::after {
|
||||
background-color: hsla(0, 0%, 100%, 0.35);
|
||||
}
|
||||
|
||||
.u-size-default {
|
||||
font-size: 30rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
|
||||
.u-size-medium {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 26rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 80rpx;
|
||||
}
|
||||
|
||||
.u-size-mini {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 22rpx;
|
||||
padding-top: 1px;
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.u-primary-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-primary-dark !important;
|
||||
}
|
||||
|
||||
.u-default-plain-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
background: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-success-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-success-dark !important;
|
||||
}
|
||||
|
||||
.u-warning-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-warning-dark !important;
|
||||
}
|
||||
|
||||
.u-error-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-error-dark !important;
|
||||
}
|
||||
|
||||
.u-info-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-info-dark !important;
|
||||
}
|
||||
|
||||
.u-default-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
border-color: $u-type-primary-dark !important;
|
||||
background-color: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-primary-hover {
|
||||
background: $u-type-primary-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-success-hover {
|
||||
background: $u-type-success-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-info-hover {
|
||||
background: $u-type-info-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-warning-hover {
|
||||
background: $u-type-warning-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-error-hover {
|
||||
background: $u-type-error-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<u-popup :blur="blur" closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="popupValue" length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
|
||||
<view class="u-calendar">
|
||||
<view class="u-calendar__header">
|
||||
<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
|
||||
{{toolTip}}
|
||||
</view>
|
||||
<slot v-else name="tooltip" />
|
||||
</view>
|
||||
<view class="u-calendar__action u-flex u-row-center">
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__text">{{ showTitle }}</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-calendar__week-day">
|
||||
<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content">
|
||||
<!-- 前置空白部分 -->
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="u-calendar__content__item"></view>
|
||||
</block>
|
||||
<view class="u-calendar__content__item" :class="{
|
||||
'u-hover-class':openDisAbled(year,month,index+1),
|
||||
'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
|
||||
'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
|
||||
}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
|
||||
@tap="dateClick(index)">
|
||||
<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
|
||||
<view>{{ index + 1 }}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__bg-month">{{month}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__bottom">
|
||||
<view class="u-calendar__bottom__choose">
|
||||
<text>{{mode == 'date' ? activeDate : startDate}}</text>
|
||||
<text v-if="endDate">至{{endDate}}</text>
|
||||
</view>
|
||||
<view class="u-calendar__bottom__btn">
|
||||
<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
|
||||
* @tutorial http://uviewui.com/components/calendar.html
|
||||
* @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
|
||||
* @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
|
||||
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
|
||||
* @property {String Number} max-year 可切换的最大年份(默认2050)
|
||||
* @property {String Number} min-year 最小可选日期(默认1950)
|
||||
* @property {String Number} min-date 可切换的最小年份(默认1950-01-01)
|
||||
* @property {String Number} max-date 最大可选日期(默认当前日期)
|
||||
* @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
|
||||
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
|
||||
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
|
||||
* @property {String} color 日期字体的默认颜色(默认#303133)
|
||||
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认10075)
|
||||
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
|
||||
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
|
||||
* @property {String} range-color 选择范围内字体颜色(默认#2979ff)
|
||||
* @property {String} start-text 起始日期底部的提示文字(默认 '开始')
|
||||
* @property {String} end-text 结束日期底部的提示文字(默认 '结束')
|
||||
* @property {String} btn-type 底部确定按钮的主题(默认 'primary')
|
||||
* @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
|
||||
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
|
||||
* @example <u-calendar v-model="show" :mode="mode"></u-calendar>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-calendar',
|
||||
emits: ["update:modelValue", "input", "change"],
|
||||
props: {
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否允许切换年份
|
||||
changeYear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许切换月份
|
||||
changeMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// date-单个日期选择,range-开始日期+结束日期选择
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'date'
|
||||
},
|
||||
// 可切换的最大年份
|
||||
maxYear: {
|
||||
type: [Number, String],
|
||||
default: 2050
|
||||
},
|
||||
// 可切换的最小年份
|
||||
minYear: {
|
||||
type: [Number, String],
|
||||
default: 1950
|
||||
},
|
||||
// 最小可选日期(不在范围内日期禁用不可选)
|
||||
minDate: {
|
||||
type: [Number, String],
|
||||
default: '1950-01-01'
|
||||
},
|
||||
/**
|
||||
* 最大可选日期
|
||||
* 默认最大值为今天,之后的日期不可选
|
||||
* 2030-12-31
|
||||
* */
|
||||
maxDate: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 弹窗顶部左右两边的圆角值
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 月份切换按钮箭头颜色
|
||||
monthArrowColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 年份切换按钮箭头颜色
|
||||
yearArrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 默认日期字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 选中|起始结束日期背景色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 选中|起始结束日期字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 范围内日期背景色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(41,121,255,0.13)'
|
||||
},
|
||||
// 范围内日期字体颜色
|
||||
rangeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// mode=range时生效,起始日期自定义文案
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// mode=range时生效,结束日期自定义文案
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
//按钮样式类型
|
||||
btnType: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 当前选中日期带选中效果
|
||||
isActiveCurrent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 切换年月是否触发事件 mode=date时生效
|
||||
isChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右上角的关闭图标
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 顶部的提示文字
|
||||
toolTip: {
|
||||
type: String,
|
||||
default: '选择日期'
|
||||
},
|
||||
// 遮罩的模糊度
|
||||
blur: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popupValue:false,
|
||||
// 星期几,值为1-7
|
||||
weekday: 1,
|
||||
weekdayArr:[],
|
||||
// 当前月有多少天
|
||||
days: 0,
|
||||
daysArr:[],
|
||||
showTitle: '',
|
||||
year: 2020,
|
||||
month: 0,
|
||||
day: 0,
|
||||
startYear: 0,
|
||||
startMonth: 0,
|
||||
startDay: 0,
|
||||
endYear: 0,
|
||||
endMonth: 0,
|
||||
endDay: 0,
|
||||
today: '',
|
||||
activeDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
isStart: true,
|
||||
min: null,
|
||||
max: null,
|
||||
weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
valueCom() {
|
||||
// #ifndef VUE3
|
||||
return this.value;
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
return this.modelValue;
|
||||
// #endif
|
||||
},
|
||||
dataChange() {
|
||||
return `${this.mode}-${this.minDate}-${this.maxDate}`;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataChange(val) {
|
||||
this.init()
|
||||
},
|
||||
valueCom(v1, v2) {
|
||||
this.popupValue = v1;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
getColor(index, type) {
|
||||
let color = type == 1 ? '' : this.color;
|
||||
let day = index + 1
|
||||
let date = `${this.year}-${this.month}-${day}`
|
||||
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
|
||||
let start = this.startDate.replace(/\-/g, '/')
|
||||
let end = this.endDate.replace(/\-/g, '/')
|
||||
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||
color = type == 1 ? this.activeBgColor : this.activeColor;
|
||||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||
color = type == 1 ? this.rangeBgColor : this.rangeColor;
|
||||
}
|
||||
return color;
|
||||
},
|
||||
init() {
|
||||
let now = new Date();
|
||||
this.year = now.getFullYear();
|
||||
this.month = now.getMonth() + 1;
|
||||
this.day = now.getDate();
|
||||
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
||||
this.activeDate = this.today;
|
||||
this.min = this.initDate(this.minDate);
|
||||
this.max = this.initDate(this.maxDate || this.today);
|
||||
this.startDate = "";
|
||||
this.startYear = 0;
|
||||
this.startMonth = 0;
|
||||
this.startDay = 0;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.isStart = true;
|
||||
this.changeData();
|
||||
},
|
||||
//日期处理
|
||||
initDate(date) {
|
||||
let fdate = date.split('-');
|
||||
return {
|
||||
year: Number(fdate[0] || 1920),
|
||||
month: Number(fdate[1] || 1),
|
||||
day: Number(fdate[2] || 1)
|
||||
}
|
||||
},
|
||||
openDisAbled: function(year, month, day) {
|
||||
let bool = true;
|
||||
let date = `${year}/${month}/${day}`;
|
||||
// let today = this.today.replace(/\-/g, '/');
|
||||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
|
||||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
|
||||
let timestamp = new Date(date).getTime();
|
||||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||
bool = false;
|
||||
}
|
||||
return bool;
|
||||
},
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
formatNum: function(num) {
|
||||
return num < 10 ? '0' + num : num + '';
|
||||
},
|
||||
//一个月有多少天
|
||||
getMonthDay(year, month) {
|
||||
let days = new Date(year, month, 0).getDate();
|
||||
return days;
|
||||
},
|
||||
getWeekday(year, month) {
|
||||
let date = new Date(`${year}/${month}/01 00:00:00`);
|
||||
return date.getDay();
|
||||
},
|
||||
checkRange(year) {
|
||||
let overstep = false;
|
||||
if (year < this.minYear || year > this.maxYear) {
|
||||
uni.showToast({
|
||||
title: "日期超出范围啦~",
|
||||
icon: 'none'
|
||||
})
|
||||
overstep = true;
|
||||
}
|
||||
return overstep;
|
||||
},
|
||||
changeMonthHandler(isAdd) {
|
||||
if (isAdd) {
|
||||
let month = this.month + 1;
|
||||
let year = month > 12 ? this.year + 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month > 12 ? 1 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
|
||||
} else {
|
||||
let month = this.month - 1;
|
||||
let year = month < 1 ? this.year - 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month < 1 ? 12 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
}
|
||||
},
|
||||
changeYearHandler(isAdd) {
|
||||
let year = isAdd ? this.year + 1 : this.year - 1;
|
||||
if (!this.checkRange(year)) {
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
},
|
||||
changeData() {
|
||||
this.days = this.getMonthDay(this.year, this.month);
|
||||
this.daysArr=this.generateArray(1,this.days)
|
||||
this.weekday = this.getWeekday(this.year, this.month);
|
||||
this.weekdayArr=this.generateArray(1,this.weekday)
|
||||
this.showTitle = `${this.year}年${this.month}月`;
|
||||
if (this.isChange && this.mode == 'date') {
|
||||
this.btnFix(true);
|
||||
}
|
||||
},
|
||||
dateClick: function(day) {
|
||||
day += 1;
|
||||
if (!this.openDisAbled(this.year, this.month, day)) {
|
||||
this.day = day;
|
||||
let date = `${this.year}-${this.month}-${day}`;
|
||||
if (this.mode == 'date') {
|
||||
this.activeDate = date;
|
||||
} else {
|
||||
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
|
||||
if (this.isStart || compare) {
|
||||
this.startDate = date;
|
||||
this.startYear = this.year;
|
||||
this.startMonth = this.month;
|
||||
this.startDay = this.day;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.activeDate = "";
|
||||
this.isStart = false;
|
||||
} else {
|
||||
this.endDate = date;
|
||||
this.endYear = this.year;
|
||||
this.endMonth = this.month;
|
||||
this.endDay = this.day;
|
||||
this.isStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
close() {
|
||||
// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
|
||||
this.$emit('input', false);
|
||||
this.$emit("update:modelValue", false);
|
||||
},
|
||||
getWeekText(date) {
|
||||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
|
||||
let week = date.getDay();
|
||||
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
|
||||
},
|
||||
btnFix(show) {
|
||||
if (!show) {
|
||||
this.close();
|
||||
}
|
||||
if (this.mode == 'date') {
|
||||
let arr = this.activeDate.split('-')
|
||||
let year = this.isChange ? this.year : Number(arr[0]);
|
||||
let month = this.isChange ? this.month : Number(arr[1]);
|
||||
let day = this.isChange ? this.day : Number(arr[2]);
|
||||
//当前月有多少天
|
||||
let days = this.getMonthDay(year, month);
|
||||
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
|
||||
let weekText = this.getWeekText(result);
|
||||
let isToday = false;
|
||||
if (`${year}-${month}-${day}` == this.today) {
|
||||
//今天
|
||||
isToday = true;
|
||||
}
|
||||
this.$emit('change', {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
days: days,
|
||||
result: result,
|
||||
week: weekText,
|
||||
isToday: isToday,
|
||||
// switch: show //是否是切换年月操作
|
||||
});
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) return;
|
||||
let startMonth = this.formatNum(this.startMonth);
|
||||
let startDay = this.formatNum(this.startDay);
|
||||
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
|
||||
let startWeek = this.getWeekText(startDate)
|
||||
|
||||
let endMonth = this.formatNum(this.endMonth);
|
||||
let endDay = this.formatNum(this.endDay);
|
||||
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
|
||||
let endWeek = this.getWeekText(endDate);
|
||||
this.$emit('change', {
|
||||
startYear: this.startYear,
|
||||
startMonth: this.startMonth,
|
||||
startDay: this.startDay,
|
||||
startDate: startDate,
|
||||
startWeek: startWeek,
|
||||
endYear: this.endYear,
|
||||
endMonth: this.endMonth,
|
||||
endDay: this.endDay,
|
||||
endDate: endDate,
|
||||
endWeek: endWeek
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-calendar {
|
||||
color: $u-content-color;
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
color: $u-main-color;
|
||||
|
||||
&__text {
|
||||
margin-top: 30rpx;
|
||||
padding: 0 60rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
padding: 40rpx 0 40rpx 0;
|
||||
|
||||
&__icon {
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: 0 16rpx;
|
||||
color: $u-main-color;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__week-day {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
&--end-date {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--start-date {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 14.2857%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&__inner {
|
||||
height: 84rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 32rpx;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
|
||||
&__desc {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
transform: scale(0.75);
|
||||
transform-origin: center center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
bottom: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__tips {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
text-align: center;
|
||||
bottom: 8rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg-month {
|
||||
position: absolute;
|
||||
font-size: 130px;
|
||||
line-height: 130px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #e4e7ed;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 0 40rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
|
||||
&__choose {
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,267 @@
|
||||
<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>
|
||||
300
uniapp/src/uni_modules/vk-uview-ui/components/u-card/u-card.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<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>
|
||||
@@ -0,0 +1,70 @@
|
||||
<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>
|
||||
@@ -0,0 +1,317 @@
|
||||
<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>
|
||||
@@ -0,0 +1,178 @@
|
||||
<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>
|
||||
@@ -0,0 +1,322 @@
|
||||
<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>
|
||||
@@ -0,0 +1,220 @@
|
||||
<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>
|
||||
156
uniapp/src/uni_modules/vk-uview-ui/components/u-col/u-col.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<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>
|
||||
@@ -0,0 +1,205 @@
|
||||
<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>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<view class="u-collapse">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* collapse 手风琴
|
||||
* @description 通过折叠面板收纳内容区域
|
||||
* @tutorial https://www.uviewui.com/components/collapse.html
|
||||
* @property {Boolean} accordion 是否手风琴模式(默认true)
|
||||
* @property {Boolean} arrow 是否显示标题右侧的箭头(默认true)
|
||||
* @property {String} arrow-color 标题右侧箭头的颜色(默认#909399)
|
||||
* @property {Object} head-style 标题自定义样式,对象形式
|
||||
* @property {Object} body-style 主体自定义样式,对象形式
|
||||
* @property {String} hover-class 样式类名,按下时有效(默认u-hover-class)
|
||||
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
|
||||
* @example <u-collapse></u-collapse>
|
||||
*/
|
||||
export default {
|
||||
name:"u-collapse",
|
||||
emits: ["change"],
|
||||
props: {
|
||||
// 是否手风琴模式
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部的样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主体的样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示右侧的箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头的颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 标题部分按压时的样式类,"none"为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-hover-class'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况
|
||||
init() {
|
||||
this.childrens.forEach((vm, index) => {
|
||||
vm.init();
|
||||
})
|
||||
},
|
||||
// collapse item被点击,由collapse item调用父组件方法
|
||||
onChange() {
|
||||
let activeItem = [];
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (vm.isShow) {
|
||||
activeItem.push(vm.nameSync);
|
||||
}
|
||||
})
|
||||
// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
|
||||
if (this.accordion) activeItem = activeItem.join('');
|
||||
this.$emit('change', activeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-notice-bar"
|
||||
:style="{
|
||||
background: computeBgColor,
|
||||
padding: padding
|
||||
}"
|
||||
:class="[
|
||||
type ? `u-type-${type}-light-bg` : ''
|
||||
]"
|
||||
>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item">
|
||||
<view
|
||||
class="u-news-item u-line-1"
|
||||
:style="[textStyle]"
|
||||
@tap="click(index)"
|
||||
:class="['u-type-' + type]"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
|
||||
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ["close", "getMore", "end"],
|
||||
props: {
|
||||
// 显示的内容,数组
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 显示的主题,success|error|primary|info|warning
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 是否显示左侧的音量图标
|
||||
volumeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右侧的右箭头图标
|
||||
moreIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右侧的关闭图标
|
||||
closeIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 文字颜色,各图标也会使用文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 滚动方向,row-水平滚动,column-垂直滚动
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 滚动一个周期的时间长,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 音量喇叭的大小
|
||||
volumeSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动时,是否采用衔接形式滚动
|
||||
isCircular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动方向,horizontal-水平滚动,vertical-垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 播放状态,play-播放,paused-暂停
|
||||
playState: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否禁止用手滑动切换
|
||||
// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通知的边距
|
||||
padding: {
|
||||
type: [Number, String],
|
||||
default: '18rpx 24rpx'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算字体颜色,如果没有自定义的,就用uview主题颜色
|
||||
computeColor() {
|
||||
if (this.color) return this.color;
|
||||
// 如果是无主题,就默认使用content-color
|
||||
else if(this.type == 'none') return '#606266';
|
||||
else return this.type;
|
||||
},
|
||||
// 文字内容的样式
|
||||
textStyle() {
|
||||
let style = {};
|
||||
if (this.color) style.color = this.color;
|
||||
else if(this.type == 'none') style.color = '#606266';
|
||||
style.fontSize = this.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
// 垂直或者水平滚动
|
||||
vertical() {
|
||||
if(this.mode == 'horizontal') return false;
|
||||
else return true;
|
||||
},
|
||||
// 计算背景颜色
|
||||
computeBgColor() {
|
||||
if (this.bgColor) return this.bgColor;
|
||||
else if(this.type == 'none') return 'transparent';
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// animation: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 点击通告栏
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
// 点击更多箭头按钮
|
||||
getMore() {
|
||||
this.$emit('getMore');
|
||||
},
|
||||
change(e) {
|
||||
let index = e.detail.current;
|
||||
if(index == this.list.length - 1) {
|
||||
this.$emit('end');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-notice-bar {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
padding: 18rpx 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-swiper {
|
||||
font-size: 26rpx;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.u-swiper-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-news-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-right-icon {
|
||||
margin-left: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-left-icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="u-count-down">
|
||||
<slot>
|
||||
<text class="u-count-down__text" :style="customStyle">{{ formattedTime }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isSameSecond, parseFormat, parseTimeData } from "./utils";
|
||||
/**
|
||||
* u-count-down 倒计时
|
||||
* @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
|
||||
* @tutorial https://uviewui.com/components/countDown.html
|
||||
* @property {String | Number} timestamp 倒计时时长,单位ms (默认 0 )
|
||||
* @property {String} format 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 (默认 'HH:mm:ss' )
|
||||
* @property {Boolean} autoStart 是否自动开始倒计时 (默认 true )
|
||||
* @event {Function} end 倒计时结束时触发
|
||||
* @event {Function} change 倒计时变化时触发
|
||||
* @event {Function} start 开始倒计时
|
||||
* @event {Function} pause 暂停倒计时
|
||||
* @event {Function} reset 重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时
|
||||
* @example <u-count-down :timestamp="timestamp"></u-count-down>
|
||||
*/
|
||||
export default {
|
||||
name: "u-count-down",
|
||||
emits: ["change", "end", "finish"],
|
||||
props: {
|
||||
// 倒计时时长,单位ms
|
||||
timestamp: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒
|
||||
format: {
|
||||
type: String,
|
||||
default: "DD:HH:mm:ss"
|
||||
},
|
||||
// 是否自动开始倒计时
|
||||
autoStart: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customStyle: {
|
||||
type: [String, Object],
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
// 各单位(天,时,分等)剩余时间
|
||||
timeData: parseTimeData(0),
|
||||
// 格式化后的时间,如"03:23:21"
|
||||
formattedTime: "0",
|
||||
// 倒计时是否正在进行中
|
||||
runing: false,
|
||||
endTime: 0, // 结束的毫秒时间戳
|
||||
remainTime: 0 // 剩余的毫秒时间
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
timestamp(n) {
|
||||
this.reset();
|
||||
},
|
||||
format(newVal, oldVal) {
|
||||
this.pause();
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.reset();
|
||||
},
|
||||
// 开始倒计时
|
||||
start() {
|
||||
if (this.runing) return;
|
||||
// 标识为进行中
|
||||
this.runing = true;
|
||||
// 结束时间戳 = 此刻时间戳 + 剩余的时间
|
||||
this.endTime = Date.now() + this.remainTime;
|
||||
this.toTick();
|
||||
},
|
||||
// 根据是否展示毫秒,执行不同操作函数
|
||||
toTick() {
|
||||
if (this.format.indexOf("SSS") > -1) {
|
||||
this.microTick();
|
||||
} else {
|
||||
this.macroTick();
|
||||
}
|
||||
},
|
||||
macroTick() {
|
||||
this.clearTimeout();
|
||||
// 每隔一定时间,更新一遍定时器的值
|
||||
// 同时此定时器的作用也能带来毫秒级的更新
|
||||
this.timer = setTimeout(() => {
|
||||
// 获取剩余时间
|
||||
const remain = this.getRemainTime();
|
||||
// 重设剩余时间
|
||||
if (!isSameSecond(remain, this.remainTime) || remain === 0) {
|
||||
this.setRemainTime(remain);
|
||||
}
|
||||
// 如果剩余时间不为0,则继续检查更新倒计时
|
||||
if (this.remainTime !== 0) {
|
||||
this.macroTick();
|
||||
}
|
||||
}, 30);
|
||||
},
|
||||
microTick() {
|
||||
this.clearTimeout();
|
||||
this.timer = setTimeout(() => {
|
||||
this.setRemainTime(this.getRemainTime());
|
||||
if (this.remainTime !== 0) {
|
||||
this.microTick();
|
||||
}
|
||||
}, 30);
|
||||
},
|
||||
// 获取剩余的时间
|
||||
getRemainTime() {
|
||||
// 取最大值,防止出现小于0的剩余时间值
|
||||
return Math.max(this.endTime - Date.now(), 0);
|
||||
},
|
||||
// 设置剩余的时间
|
||||
setRemainTime(remain) {
|
||||
this.remainTime = remain;
|
||||
// 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
|
||||
const timeData = parseTimeData(remain);
|
||||
this.$emit("change", timeData);
|
||||
// 得出格式化后的时间
|
||||
this.formattedTime = parseFormat(this.format, timeData);
|
||||
// 如果时间已到,停止倒计时
|
||||
if (remain <= 0) {
|
||||
this.pause();
|
||||
this.$emit("end");
|
||||
this.$emit("finish");
|
||||
}
|
||||
},
|
||||
// 重置倒计时
|
||||
reset() {
|
||||
this.pause();
|
||||
this.remainTime = this.timestamp;
|
||||
this.setRemainTime(this.remainTime);
|
||||
if (this.autoStart) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
// 暂停倒计时
|
||||
pause() {
|
||||
this.runing = false;
|
||||
this.clearTimeout();
|
||||
},
|
||||
// 清空定时器
|
||||
clearTimeout() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimeout();
|
||||
},
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimeout();
|
||||
},
|
||||
// #endif
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -0,0 +1,62 @@
|
||||
// 补0,如1 -> 01
|
||||
function padZero(num, targetLength = 2) {
|
||||
let str = `${num}`
|
||||
while (str.length < targetLength) {
|
||||
str = `0${str}`
|
||||
}
|
||||
return str
|
||||
}
|
||||
const SECOND = 1000
|
||||
const MINUTE = 60 * SECOND
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
export function parseTimeData(time) {
|
||||
const days = Math.floor(time / DAY)
|
||||
const hours = Math.floor((time % DAY) / HOUR)
|
||||
const minutes = Math.floor((time % HOUR) / MINUTE)
|
||||
const seconds = Math.floor((time % MINUTE) / SECOND)
|
||||
const milliseconds = Math.floor(time % SECOND)
|
||||
return {
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
milliseconds
|
||||
}
|
||||
}
|
||||
export function parseFormat(format, timeData) {
|
||||
let {
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
milliseconds
|
||||
} = timeData
|
||||
// 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
|
||||
if (format.indexOf('DD') === -1) {
|
||||
hours += days * 24
|
||||
} else {
|
||||
// 对天补0
|
||||
format = format.replace('DD', padZero(days))
|
||||
}
|
||||
// 其他同理于DD的格式化处理方式
|
||||
if (format.indexOf('HH') === -1) {
|
||||
minutes += hours * 60
|
||||
} else {
|
||||
format = format.replace('HH', padZero(hours))
|
||||
}
|
||||
if (format.indexOf('mm') === -1) {
|
||||
seconds += minutes * 60
|
||||
} else {
|
||||
format = format.replace('mm', padZero(minutes))
|
||||
}
|
||||
if (format.indexOf('ss') === -1) {
|
||||
milliseconds += seconds * 1000
|
||||
} else {
|
||||
format = format.replace('ss', padZero(seconds))
|
||||
}
|
||||
return format.replace('SSS', padZero(milliseconds, 3))
|
||||
}
|
||||
export function isSameSecond(time1, time2) {
|
||||
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-count-num"
|
||||
:style="{
|
||||
fontSize: fontSize + 'rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: color
|
||||
}"
|
||||
>
|
||||
{{ displayValueCom }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* countTo 数字滚动
|
||||
* @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
|
||||
* @tutorial https://www.uviewui.com/components/countTo.html
|
||||
* @property {String Number} nullVal 空值或NaN时显示的值,默认 -
|
||||
* @property {String Number} start-val 开始值
|
||||
* @property {String Number} end-val 结束值
|
||||
* @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
|
||||
* @property {Boolean} autoplay 是否自动开始滚动(默认true)
|
||||
* @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
|
||||
* @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
|
||||
* @property {String} separator 千位分隔符,见官网说明
|
||||
* @property {String} color 字体颜色(默认#303133)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认50)
|
||||
* @property {Boolean} bold 字体是否加粗(默认false)
|
||||
* @event {Function} end 数值滚动到目标值时触发
|
||||
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
|
||||
*/
|
||||
export default {
|
||||
name: "u-count-to",
|
||||
emits: ["end"],
|
||||
props: {
|
||||
// 没有值时显示
|
||||
nullVal: {
|
||||
type: [Number, String],
|
||||
default: "-"
|
||||
},
|
||||
// 开始的数值,默认从0增长到某一个数
|
||||
startVal: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 要滚动的目标数值,必须
|
||||
endVal: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 滚动到目标数值的动画持续时间,单位为毫秒(ms)
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 设置数值后是否自动开始滚动
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 要显示的小数位数
|
||||
decimals: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 是否在即将到达目标数值的时候,使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 十进制分割
|
||||
decimal: {
|
||||
type: [Number, String],
|
||||
default: "."
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#303133"
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 千位分隔符,类似金额的分割(¥23,321.05中的",")
|
||||
separator: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
printVal: null,
|
||||
paused: false, // 是否暂停
|
||||
localDuration: Number(this.duration),
|
||||
startTime: null, // 开始的时间
|
||||
timestamp: null, // 时间戳
|
||||
remaining: null, // 停留的时间
|
||||
rAF: null,
|
||||
lastTime: 0 // 上一次的时间
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal;
|
||||
},
|
||||
displayValueCom() {
|
||||
let str;
|
||||
let { displayValue, nullVal } = this;
|
||||
if (isNaN(displayValue)) {
|
||||
str = nullVal;
|
||||
} else {
|
||||
str = displayValue;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
methods: {
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
|
||||
},
|
||||
requestAnimationFrame(callback) {
|
||||
const currTime = new Date().getTime();
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
|
||||
const id = setTimeout(() => {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
this.lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
},
|
||||
|
||||
cancelAnimationFrame(id) {
|
||||
clearTimeout(id);
|
||||
},
|
||||
// 开始滚动数字
|
||||
start() {
|
||||
this.localStartVal = this.startVal;
|
||||
this.startTime = null;
|
||||
this.localDuration = this.duration;
|
||||
this.paused = false;
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 暂定状态,重新再开始滚动;或者滚动状态下,暂停
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume();
|
||||
this.paused = false;
|
||||
} else {
|
||||
this.stop();
|
||||
this.paused = true;
|
||||
}
|
||||
},
|
||||
// 暂停
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
},
|
||||
// 重新开始(暂停的情况下)
|
||||
resume() {
|
||||
this.startTime = null;
|
||||
this.localDuration = this.remaining;
|
||||
this.localStartVal = this.printVal;
|
||||
this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null;
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
this.displayValue = this.formatNumber(this.startVal);
|
||||
},
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp;
|
||||
this.timestamp = timestamp;
|
||||
const progress = timestamp - this.startTime;
|
||||
this.remaining = this.localDuration - progress;
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
|
||||
} else {
|
||||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
|
||||
}
|
||||
this.displayValue = this.formatNumber(this.printVal);
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
} else {
|
||||
this.$emit("end");
|
||||
}
|
||||
},
|
||||
// 判断是否数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val));
|
||||
},
|
||||
formatNumber(num) {
|
||||
// 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
|
||||
num = Number(num);
|
||||
num = num.toFixed(Number(this.decimals));
|
||||
num += "";
|
||||
const x = num.split(".");
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? this.decimal + x[1] : "";
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (this.separator && !this.isNumber(this.separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, "$1" + this.separator + "$2");
|
||||
}
|
||||
}
|
||||
return x1 + x2;
|
||||
},
|
||||
// #ifndef VUE3
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
},
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<view class="u-divider" :style="{
|
||||
height: height == 'auto' ? 'auto' : height + 'rpx',
|
||||
backgroundColor: bgColor,
|
||||
marginBottom: marginBottom + 'rpx',
|
||||
marginTop: marginTop + 'rpx'
|
||||
}" @tap="click">
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
<view v-if="useSlot" class="u-divider-text" :style="{
|
||||
color: color,
|
||||
fontSize: fontSize + 'rpx'
|
||||
}"><slot /></view>
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* divider 分割线
|
||||
* @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
|
||||
* @tutorial https://www.uviewui.com/components/divider.html
|
||||
* @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx
|
||||
* @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6)
|
||||
* @property {String} color 文字颜色(默认#909399)
|
||||
* @property {String Number} fontSize 字体大小,单位rpx(默认26)
|
||||
* @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff)
|
||||
* @property {String Number} height 整个divider的高度,单位rpx(默认40)
|
||||
* @property {String} type 将线条设置主题色(默认primary)
|
||||
* @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true)
|
||||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
|
||||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
|
||||
* @event {Function} click divider组件被点击时触发
|
||||
* @example <u-divider color="#fa3534">长河落日圆</u-divider>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-divider',
|
||||
props: {
|
||||
// 单一边divider横线的宽度(数值),单位rpx。或者百分比
|
||||
halfWidth: {
|
||||
type: [Number, String],
|
||||
default: 150
|
||||
},
|
||||
// divider横线的颜色,如设置,
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 主题色,可以是primary|info|success|warning|error之一值
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 整个divider的背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 整个divider的高度单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 上边距
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 下边距
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lineStyle() {
|
||||
let style = {};
|
||||
if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth;
|
||||
else style.width = this.halfWidth + 'rpx';
|
||||
// borderColor优先级高于type值
|
||||
if(this.borderColor) style.borderColor = this.borderColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-divider {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.u-divider-line {
|
||||
border-bottom: 1px solid $u-border-color;
|
||||
transform: scale(1, 0.5);
|
||||
transform-origin: center;
|
||||
|
||||
&--bordercolor--primary {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--success {
|
||||
border-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--bordercolor--error {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--info {
|
||||
border-color: $u-type-info;
|
||||
}
|
||||
|
||||
&--bordercolor--warning {
|
||||
border-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.u-divider-text {
|
||||
white-space: nowrap;
|
||||
padding: 0 16rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||