修复已知问题

This commit is contained in:
bobobobo
2026-01-29 00:27:31 +08:00
parent 990f2df972
commit 41c1e5ba89
36 changed files with 1353 additions and 169 deletions

4
.env
View File

@@ -1,6 +1,6 @@
# API
# VITE_SYSTEM_URL = "http://ha699de6.natappfree.cc"
VITE_SYSTEM_URL = "https://dev.cqjcteach.cn/prod-api"
VITE_SYSTEM_URL = "http://ea252b67.natappfree.cc"
# VITE_SYSTEM_URL = "https://dev.cqjcteach.cn/prod-api"
# 第三方客户 channelId
VITE_CHANNEL_ID = "7b81ec142eca42baa045820793b821bb"

View File

@@ -30,11 +30,12 @@
import backSVG from '../../../assets/icon/back.svg'
import { useUI } from '../../../../utils/use-ui'
import { endUserService } from '../../../../api/my-index'
import { navigateBack, reLaunch } from '../../../../utils/router'
const { showDialog, showToast } = useUI()
const emits = defineEmits(['openGroupManagement'])
const props = defineProps(['isGroup', 'serviceID'])
const props = defineProps(['isGroup', 'serviceID', 'type'])
const currentConversation = ref<IConversationModel>()
const typingStatus = ref(false)
@@ -91,15 +92,19 @@
}
const back = async () => {
if (props.type === 'GROUP') {
reLaunch('/')
return
}
if (props.serviceID) {
const show = await showDialog('提示', '确定要退出当前会话吗?')
if (show) {
await endUserService(props.serviceID)
await showToast('结束服务成功')
uni.navigateBack()
navigateBack()
}
} else {
uni.navigateBack()
navigateBack()
}
}
</script>

View File

@@ -1,5 +1,6 @@
<template>
<div class="chat" :style="{ marginBottom: keywordHight + 'px' }">
<TUICallKit></TUICallKit>
<div :class="['tui-chat', !isPC && 'tui-chat-h5']">
<div
v-if="!currentConversationID"
@@ -15,6 +16,7 @@
:isGroup="!isNotInGroup ? isGroup : null"
:headerExtensionList="headerExtensionList"
:serviceID="serviceID"
:type="backStatusName"
@closeChat="closeChat"
@openGroupManagement="handleGroup"
/>
@@ -101,6 +103,7 @@
TUIConstants,
ExtensionInfo
} from '@tencentcloud/tui-core-lite'
import { TUICallKit } from '@trtc/calls-uikit-vue'
import ChatHeader from './chat-header/index.vue'
import MessageList from './message-list/index.vue'
import MessageInput from './message-input/index.vue'
@@ -121,7 +124,11 @@
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { initChat, logout } from './entry-chat-only.ts'
// 返回状态
const backStatusName = ref('')
onLoad(options => {
backStatusName.value = options?.type || ''
serviceID.value = options?.id || ''
initChat(options)
})

View File

@@ -142,7 +142,7 @@
v-else-if="item.type === TYPES.MSG_LOCATION"
:content="item.getMessageContent()"
/>
<!-- 自定义消息,目前只支持红包 -->
<!-- 自定义消息,目前只支持红包,商品详情 -->
<MessageCustom
v-else-if="item.type === TYPES.MSG_CUSTOM"
ref="customRefMessage"

View File

@@ -20,6 +20,8 @@
>
<Avatar
useSkeletonAnimation
:type="message.conversationType"
:id="message.from"
:url="message.avatar || ''"
:style="{ flex: '0 0 auto' }"
/>

View File

@@ -100,6 +100,22 @@
</text> -->
</view>
</template>
<!-- 商品详情 -->
<template
v-else-if="customData.businessID === CHAT_MSG_CUSTOM_TYPE.GOODS"
>
<view class="goods-box" @click="onGoods">
<image
mode="heightFix"
:src="goodsData.cover"
class="img-box"
></image>
<view class="right-box">
<text class="name">{{ goodsData.title }}</text>
<text class="price">¥ {{ goodsData.price }}</text>
</view>
</view>
</template>
<template v-else>
<span v-html="content.custom" />
</template>
@@ -125,6 +141,7 @@
import star from '../../../../assets/icon/star-light.png'
import unopenedEnvelope from '../../../../assets/icon/unopened-envelope.svg'
import kaiEnvelope from '../../../../assets/icon/kai-unopened-envelope.svg'
import { navigateTo } from '../../../../../utils/router'
interface Props {
messageItem: IMessageModel
@@ -173,6 +190,16 @@
}
})
/** 商品详情显示数据 */
const goodsData = computed(() => {
return JSON.parse(props.messageItem.payload.data)
})
/** 点击商品详情 */
const onGoods = () => {
navigateTo('/pages/mall/detail', { productId: goodsData.value.id })
}
watchEffect(() => {
custom.value = props.content
message.value = props.messageItem
@@ -298,4 +325,38 @@
color: #ffffff;
}
}
.goods-box {
display: flex;
.img-box {
flex-shrink: 0;
width: 180rpx;
height: 240rpx;
border-radius: 12rpx;
overflow: hidden;
background: rgb(182, 182, 182);
margin-right: 20rpx;
}
.right-box {
width: 50vw;
display: flex;
flex-direction: column;
justify-content: space-between;
.name {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
font-size: 28rpx;
font-weight: 400;
color: #333333;
}
.price {
font-size: 28rpx;
font-weight: 500;
color: #ff7201;
}
}
}
</style>

View File

@@ -171,7 +171,7 @@ const isHoverFingerPointer = computed<boolean>(() => {
function openReadUserPanel() {
if (isHoverFingerPointer.value) {
emits('openReadUserPanel');
// emits('openReadUserPanel');
}
}

View File

@@ -380,19 +380,25 @@
/** 是否最开始创建群聊 */
const isFirstCreateGroup = (item: IConversationModel) => {
// 打印
if (item.type === 'GROUP' && item?.lastMessage?.payload?.data) {
const data = JSON.parse(item?.lastMessage?.payload?.data)
return data.content === 'Create Group'
? `${item.getLastMessage('text')?.split(':')[0]}:创建群聊`
: ''
}
// if (item.type === 'GROUP' && item?.lastMessage?.payload?.data) {
// const data = JSON.parse(item?.lastMessage?.payload?.data)
// return data.content === 'Create Group'
// ? `${item.getLastMessage('text')?.split(':')[0]}:创建群聊`
// : ''
// }
if (item?.lastMessage?.payload?.data) {
const data = JSON.parse(item?.lastMessage?.payload?.data)
const text = item.getLastMessage('text')?.split(':')
const isText = text && text.length > 1
if (data.businessID === CHAT_MSG_CUSTOM_TYPE.GOODS) {
console.log(item, '==2222222=')
return `自定义数据--`
if (isText) {
return `${text[0]}:[商品信息] ${data.title}`
} else {
return `[商品信息]:${data.title}`
}
}
if (data.content === 'Create Group' && item.type === 'GROUP') {
return `${item.getLastMessage('text')?.split(':')[0]}:创建群聊`
}
return ''
}

View File

@@ -6,6 +6,7 @@
height: avatarSize,
borderRadius: '80rpx'
}"
@click="onTopAvatar"
>
<template v-if="isUniFrameWork">
<image
@@ -42,14 +43,26 @@
</template>
<script setup lang="ts">
import TUICore, { TUIConstants } from '@tencentcloud/tui-core-lite'
import { ref, toRefs } from '../../../adapter-vue'
import { isUniFrameWork } from '../../../utils/env'
import TUIChatEngine, {
TUIFriendService
} from '@tencentcloud/chat-uikit-engine-lite'
import { useAuthUser } from '../../../../composables/useAuthUser'
import { navigateTo } from '@/utils/router'
import { useUI } from '../../../../utils/use-ui'
const { tencentUserSig } = useAuthUser()
const { showLoading, hideLoading } = useUI()
interface IProps {
url: string
size?: string
borderRadius?: string
useSkeletonAnimation?: boolean
id?: string
type?: string
}
interface IEmits {
@@ -66,7 +79,9 @@
url: 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png',
size: '36px',
borderRadius: '5px',
useSkeletonAnimation: false
useSkeletonAnimation: false,
id: '',
type: ''
})
const {
@@ -97,6 +112,52 @@
}
emits('onError', e)
}
/** 点击头像跳转 */
const onTopAvatar = () => {
console.log(props.type)
if (props.id) {
showLoading()
TUICore.callService({
serviceName: TUIConstants.TUISearch.SERVICE.NAME,
method: TUIConstants.TUISearch.SERVICE.METHOD.SEARCH_USER,
params: {
userID: props.id
}
})
.then(res => {
console.log(res.data, '==')
const searchList = res.data
if (searchList.length > 0) {
TUIFriendService.checkFriend({
type: TUIChatEngine.TYPES.SNS_CHECK_TYPE_BOTH,
userIDList: [searchList[0].userID]
})
.then(v => {
const isShow =
v.data.successUserIDList[0].relation ===
TUIChatEngine.TYPES.SNS_TYPE_BOTH_WAY
hideLoading()
navigateTo('/pages/adduser/details', {
id: searchList[0].userID,
state: props.type,
type: isShow
? '1'
: searchList[0].userID == tencentUserSig.value.userId
? '99'
: ''
})
})
.catch(() => {
hideLoading()
})
}
})
.catch(() => {
hideLoading()
})
}
}
</script>
<style scoped lang="scss">
@@ -122,9 +183,7 @@
width: 100%;
height: 100%;
background-color: #ececec;
transition:
opacity 0.3s,
background-color 0.1s ease-out;
transition: opacity 0.3s, background-color 0.1s ease-out;
&.skeleton-animation {
animation: breath 2s linear 0.3s infinite;

View File

@@ -70,3 +70,12 @@ export const getGroupDetail = id => {
loading: false
})
}
/** 获取可参与的拼团列表 */
export const getParticipateList = id => {
return http({
url: `/api/service/groupOrder/available/${id}`,
method: 'get',
loading: false
})
}

View File

@@ -35,6 +35,11 @@
cover: {
type: String,
default: ''
},
/** 多少钱 */
price: {
type: [String, Number],
default: ''
}
})
@@ -119,59 +124,71 @@
}
}
/** 发送自定义信息数据 */
const sendCustomData = async item => {
let to = ''
let isGroup = false
if (item.id.includes('GROUP')) {
// 分割 GROUP
to = item.id.split('GROUP')[1]
isGroup = true
} else {
to = item.id.split('C2C')[1]
isGroup = false
}
const payload = {
data: JSON.stringify({
id: props.id,
businessID: CHAT_MSG_CUSTOM_TYPE.GOODS,
title: props.text,
cover: props.cover,
price: props.price
}),
description: props.text,
extension: props.text
}
const options = {
to,
payload,
conversationType: isGroup
? TUIChatEngine.TYPES.CONV_GROUP
: TUIChatEngine.TYPES.CONV_C2C,
needReadReceipt: isEnabledMessageReadReceiptGlobal()
}
await TUIChatService.sendCustomMessage(options)
}
/** 多选分享 */
const multiSelectShare = async () => {
showLoading()
const requests = selectedList.value.map(v => sendCustomData(v))
await Promise.all(requests)
hideLoading()
await showToast('分享成功', 'success')
isShow.value = false
}
/**
* 确定分享
* @param state 1 多选 0 单选
* @param data
*/
const onConfirm = async (state, item) => {
const show = await showDialog(
'提示',
`确定分享${props.type == 1 ? '直播间' : '商品'}吗?`
)
if (!show) {
return
}
if (state) {
console.log('多选分享?')
multiSelectShare()
} else {
// props
const show = await showDialog(
'提示',
`确定分享${props.type == 1 ? '直播间' : '商品'}吗?`
)
if (!show) {
return
}
console.log('单选数据', item.id)
// 字符串匹配 GROUP C2C
let to = ''
let isGroup = false
if (item.id.includes('GROUP')) {
// 分割 GROUP
to = item.id.split('GROUP')[1]
isGroup = true
} else {
to = item.id.split('C2C')[1]
isGroup = false
}
const payload = {
data: JSON.stringify({
id: props.id,
businessID: CHAT_MSG_CUSTOM_TYPE.GOODS,
title: props.text,
cover: props.cover
}),
description: props.text,
extension: props.text
}
const options = {
to,
payload,
conversationType: isGroup
? TUIChatEngine.TYPES.CONV_GROUP
: TUIChatEngine.TYPES.CONV_C2C,
needReadReceipt: isEnabledMessageReadReceiptGlobal()
}
showLoading()
await TUIChatService.sendCustomMessage(options)
await sendCustomData(item)
hideLoading()
await showToast('分享成功', 'success')
isShow.value = false
console.log(options)
}
}

58
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@tencentcloud/chat-uikit-uniapp": "3.1.0",
"@tencentcloud/tui-core-lite": "1.0.0",
"@tencentcloud/universal-api": "^2.4.0",
"@trtc/calls-uikit-vue": "^4.4.2",
"dayjs": "^1.11.10",
"unplugin-vue2-script-setup": "^0.11.4"
}
@@ -766,15 +767,46 @@
}
},
"node_modules/@trtc/calls-uikit-vue": {
"version": "4.2.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue/-/calls-uikit-vue-4.2.2.tgz",
"integrity": "sha512-xAHBo2RXb/PDCcd07sdftgE2HtiW3QC3hXY8g5yWfTkJ3WIPfDi5OsG9lti0KgzF9ZnfMf4i08ptAoC3k72iHQ==",
"version": "4.4.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue/-/calls-uikit-vue-4.4.2.tgz",
"integrity": "sha512-a5oaNSO4qN0FSsDb65Kl9BEghDdWV0l5RdjMgaWEjrqNVVCuz7p5s7/6AYaB78dpdsIC9U9FXTVbM2+u79r4zw==",
"license": "ISC",
"peer": true,
"dependencies": {
"@tencentcloud/lite-chat": "^1.5.0",
"@tencentcloud/lite-chat": "^1.6.3",
"@tencentcloud/tui-core-lite": "1.0.0",
"@trtc/call-engine-lite-js": "~3.4.0"
"@trtc/call-engine-lite-js": "~3.5.0"
}
},
"node_modules/@trtc/calls-uikit-vue/node_modules/@trtc/call-engine-lite-js": {
"version": "3.5.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/call-engine-lite-js/-/call-engine-lite-js-3.5.2.tgz",
"integrity": "sha512-qwZTOO99dYIlGTrg++iVmxTSZzQ3yHyJ2NvOyibCttgQG2By3fJZNYN1thk7JG3C96FcCZ3+pjGqfRheG79pMA==",
"license": "ISC",
"dependencies": {
"@tencentcloud/lite-chat": "^1.6.3",
"core-js": "^3.8.3",
"eventemitter3": "^4.0.7",
"rtc-detect": "^0.0.5",
"trtc-cloud-js-sdk": "2.10.10",
"tuikit-logger": "latest"
}
},
"node_modules/@trtc/calls-uikit-vue/node_modules/trtc-cloud-js-sdk": {
"version": "2.10.10",
"resolved": "https://repo.huaweicloud.com/repository/npm/trtc-cloud-js-sdk/-/trtc-cloud-js-sdk-2.10.10.tgz",
"integrity": "sha512-nYBR4bQnwMCrXeOuN+ih8OgPjYd4JynBatVWHPa5h/PGdeHuldpN0Fi+4f0+ybJ0SYY82ksFetNAh6888rXSOA==",
"license": "ISC",
"dependencies": {
"trtc-sdk-v5": "5.15.1-beta.8"
}
},
"node_modules/@trtc/calls-uikit-vue/node_modules/trtc-sdk-v5": {
"version": "5.15.1-beta.8",
"resolved": "https://repo.huaweicloud.com/repository/npm/trtc-sdk-v5/-/trtc-sdk-v5-5.15.1-beta.8.tgz",
"integrity": "sha512-3u5P3O9aS33Ja+HFTDiFF6/hkq7+KlfslnInSyYeHuAr5b6p26k6rBn3PZ6kXf+onzQhnCV1q2mMPePKgkomig==",
"license": "ISC",
"dependencies": {
"webrtc-adapter": "^8.2.3"
}
},
"node_modules/@trtc/calls-uikit-vue2.6": {
@@ -1373,7 +1405,6 @@
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
@@ -1542,8 +1573,7 @@
"version": "4.0.7",
"resolved": "https://repo.huaweicloud.com/repository/npm/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/events": {
"version": "3.3.0",
@@ -1969,8 +1999,7 @@
"version": "0.0.5",
"resolved": "https://repo.huaweicloud.com/repository/npm/rtc-detect/-/rtc-detect-0.0.5.tgz",
"integrity": "sha512-VANIELbaoIkZRj4gyiCCbTM+/ASy0eNgF35jCs+rrGxzYvD7YIBajEbGGZeh+5ZCNAX8/rT8IVRdpuallf174Q==",
"license": "ISC",
"peer": true
"license": "ISC"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
@@ -2072,8 +2101,7 @@
"version": "3.2.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/sdp/-/sdp-3.2.1.tgz",
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/semver": {
"version": "6.3.1",
@@ -2321,8 +2349,7 @@
"version": "0.0.4-beta.1",
"resolved": "https://repo.huaweicloud.com/repository/npm/tuikit-logger/-/tuikit-logger-0.0.4-beta.1.tgz",
"integrity": "sha512-Ky83B1p88xakmfZ2f92cU0YxfolyxnQBv14tQpvnuHcMTnVR2Rjy8tityDGwF+pnxrAhJ7H7OPB/4rFdWVncIw==",
"license": "ISC",
"peer": true
"license": "ISC"
},
"node_modules/typescript": {
"version": "4.9.5",
@@ -2580,7 +2607,6 @@
"resolved": "https://repo.huaweicloud.com/repository/npm/webrtc-adapter/-/webrtc-adapter-8.2.4.tgz",
"integrity": "sha512-VwtwbYNKnVQW8koB9qb8YcxNwpSVHTvvKEZLzY6uQ3gFrA9E87VPbB5xE+m1AGwUjL1UgN35jRR9hQgteZI5bg==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"sdp": "^3.2.0"
},

View File

@@ -14,7 +14,8 @@
"@tencentcloud/chat-uikit-uniapp": "3.1.0",
"@tencentcloud/tui-core-lite": "1.0.0",
"@tencentcloud/universal-api": "^2.4.0",
"unplugin-vue2-script-setup": "^0.11.4",
"dayjs": "^1.11.10"
"@trtc/calls-uikit-vue": "^4.4.2",
"dayjs": "^1.11.10",
"unplugin-vue2-script-setup": "^0.11.4"
}
}

View File

@@ -437,14 +437,15 @@
{
"path": "pages/adduser/index",
"style": {
"navigationBarTitleText": "添加地址",
"navigationBarTitleText": "添加朋友/群组",
"navigationBarBackgroundColor": "#ffffff"
}
},
{
"path": "pages/adduser/details",
"style": {
"navigationBarTitleText": "发送好友申请"
"navigationBarTitleText": "发送好友申请",
"navigationBarBackgroundColor": "#ffffff"
}
},
{

View File

@@ -93,6 +93,7 @@
}
onLoad(e => {
formData.defaultAddress = e?.defaultAddress == 1
if (props.type === 'edit') {
getData(e.id)
}

View File

@@ -21,7 +21,9 @@
}
const onAdd = () => {
navigateTo('/pages/address/add')
navigateTo('/pages/address/add', {
defaultAddress: listData.value.length === 0 ? '1' : ''
})
}
const onGo = id => {

View File

@@ -33,6 +33,8 @@
const isBlack = ref(false)
/** 点击备注弹框 */
const showRemark = ref(false)
/** 好友详情进入状态C2C 个人 GROUP 群组 */
const isDetailState = ref('')
/** 点击查看头像 */
const onViewAvatar = url => {
uni.previewImage({
@@ -87,8 +89,11 @@
})
}
/** 获取好友信息 */
const getFriendInfo = async () => {
/**
* 获取好友信息
* @param state 99 为自己
*/
const getFriendInfo = async state => {
loading.value = true
showLoading()
if (isDetail.value) {
@@ -97,10 +102,10 @@
})
.then(res => {
const data = res.data.friendList[0]
friendInfo.value = data.profile
friendInfo.value = { ...data.profile, cbType: 'C2C' }
confirmRemark.value = data.remark
remark.value = data.remark
console.log('好友信息==', data)
console.log('好友信息==', friendInfo.value)
})
.finally(() => {
loading.value = false
@@ -111,7 +116,10 @@
userIDList: [userId.value]
})
.then(res => {
friendInfo.value = res.data[0]
friendInfo.value = {
...res.data[0],
cbType: state == 99 ? 'me' : 'C2C'
}
console.log('获取好友信息成功', friendInfo.value)
})
.finally(() => {
@@ -291,31 +299,38 @@
/** 发送消息 */
const onSendMessage = () => {
if (isDetailState.value == 'C2C') {
navigateBack()
return
}
const data =
friendInfo.value?.cbType === 'group'
? `GROUP${friendInfo.value?.groupID}`
: `C2C${friendInfo.value.userID}`
TUIConversationService.switchConversation(data).then(() => {
TUIGlobal?.navigateTo({
url: `/TUIKit/components/TUIChat/index`
url: `/TUIKit/components/TUIChat/index?type=${
isDetailState.value == 'GROUP' ? 'GROUP' : ''
}`
})
})
}
onLoad(e => {
isDetailState.value = e?.state || ''
userId.value = e?.id || ''
/** type: 不传为添加 1 为详情页 9 为群信息 */
/** type: 不传为添加 1 为详情页 9 为群信息 99 为自己 */
if (e?.type == 9) {
uni.setNavigationBarTitle({
title: '群聊信息'
})
getGroupInfo()
} else {
isDetail.value = e?.type == 1 || false
isDetail.value = ['1'].includes(e?.type) || false
uni.setNavigationBarTitle({
title: isDetail.value ? '好友信息' : '发送好友申请'
})
getFriendInfo()
getFriendInfo(e?.type)
}
})
</script>
@@ -334,18 +349,23 @@
<uni-icons v-else type="contact-filled" size="80"></uni-icons>
<view class="right-box">
<text>{{ friendInfo.nick || '未知名称' }}</text>
<text v-if="!friendInfo?.cbType == 'group'">
<text v-if="friendInfo?.cbType !== 'group'">
手机号: {{ friendInfo.userID }}
</text>
<text v-if="!friendInfo?.cbType == 'group'">
<text v-if="friendInfo?.cbType !== 'group'">
个性签名: {{ friendInfo.selfSignature || '暂无个性签名' }}
</text>
<text v-else>ID: {{ friendInfo.groupID }}</text>
<text v-else>
ID: {{ friendInfo.groupID || friendInfo.userID }}
</text>
</view>
</view>
<!-- 验证信息输入 -->
<view v-if="!isDetail" class="input-wrapper">
<view
v-if="!isDetail && friendInfo.cbType !== 'me'"
class="input-wrapper"
>
<text class="title">请填写验证信息</text>
<textarea
v-model="verificationInfo"
@@ -357,7 +377,7 @@
<!-- 备注 -->
<view
v-if="!friendInfo?.cbType == 'group' && !isDetail"
v-if="!['group', 'me'].includes(friendInfo?.cbType) && !isDetail"
class="remark"
>
<text>备注名</text>
@@ -370,13 +390,33 @@
</view>
<!-- 发送申请按钮 -->
<view v-if="!isDetail" class="send-btn" @tap="submit">
<view
v-if="!isDetail && friendInfo.cbType !== 'me'"
class="send-btn"
@tap="submit"
>
<text>发送申请</text>
</view>
<!-- 去朋友圈 -->
<view
v-if="friendInfo?.cbType == 'me'"
class="remark"
@click="
navigateTo('/pages/discover/dynamic/dynamic', {
id: friendInfo.userID
})
"
>
<text>朋友圈</text>
<view style="display: flex; align-items: center">
<uni-icons type="right" color="#999999" size="36rpx"></uni-icons>
</view>
</view>
<!-- 修改好友信息======================== -->
<view
v-if="!friendInfo?.cbType == 'group' && isDetail"
v-if="friendInfo?.cbType !== 'group' && isDetail"
class="remark"
@click="showRemark = true"
>
@@ -391,15 +431,14 @@
</view>
</view>
<view
v-if="!friendInfo?.cbType == 'group' && isDetail"
v-if="friendInfo?.cbType !== 'group' && isDetail"
class="remark"
>
<text>加入黑名单</text>
<SwitchBar :value="isBlack" @click="switchChange" />
</view>
<view
v-if="!friendInfo?.cbType == 'group' && isDetail"
v-if="friendInfo?.cbType !== 'group' && isDetail"
class="send-btn"
@tap="onDeleteFriend"
>

View File

@@ -7,7 +7,9 @@
TUIFriendService,
TUIGroupService
} from '@tencentcloud/chat-uikit-engine-lite'
import { useAuthUser } from '../../composables/useAuthUser'
const { tencentUserSig } = useAuthUser()
const { showLoading, hideLoading } = useUI()
const loading = ref(false)
const searchValue = ref('')
@@ -97,7 +99,10 @@
}
const onAdd = item => {
navigateTo('/pages/adduser/details', { id: item.userID })
navigateTo('/pages/adduser/details', {
id: item.userID,
type: item.userID == tencentUserSig.value.userId ? '99' : ''
})
}
const onDetails = (item, state) => {
@@ -155,8 +160,10 @@
<text>{{ item.nick || '未知名称' }}</text>
<text>{{ item.userID }}</text>
</view>
<text v-if="isFriend" class="tag">已添加</text>
<button v-else @click.stop="onAdd(item)">添加</button>
<view v-if="item.userID !== tencentUserSig.userId">
<text v-if="isFriend" class="tag">添加</text>
<button v-else @click.stop="onAdd(item)">添加</button>
</view>
</view>
</view>
<!-- 群列表 -->
@@ -180,7 +187,8 @@
<text>{{ item.name || '未知名称' }}</text>
<text>{{ item.groupID }}</text>
</view>
<text class="tag-but">群聊</text>
<!-- <text class="tag-but">群聊</text> -->
<button style="background: #828bff">群聊</button>
<!-- <text v-if="isFriend" class="tag">已添加</text>
<button v-else @click.stop="onAdd(item)">添加</button> -->
</view>

View File

@@ -40,6 +40,7 @@
const contentData = ref('')
const inputId = ref('')
const isType = ref('')
const targetUserId = ref('')
const onScroll = e => {
cbNavBar.value?.updateScroll(e.detail.scrollTop)
@@ -57,7 +58,7 @@
const res = await getUserMomentsList({
pageNum,
pageSize,
targetUserId: isType.value == 1 ? userInfo.value.userId : ''
targetUserId: isType.value == 1 ? userInfo.value.userId : targetUserId.value
})
const list = res.rows.map(item => {
return {
@@ -123,6 +124,7 @@
})
onLoad(async e => {
targetUserId.value = e?.id || ''
isType.value = e?.type || ''
})
</script>

View File

@@ -84,12 +84,13 @@
const getData = async productId => {
const res = await getProductDetail(productId)
viewData.value = res.data
const {
id,
price,
stockQuantity,
originalPrice: nub
} = res.data.skuList.find(v => v.isDefault == 1)
} = res.data.skuList[0]
originalPrice.value = nub
formData.maxNum = stockQuantity
formData.spec = id
@@ -142,7 +143,7 @@
const res = await addOrder(data)
await refreshUserInfo()
await showToast('订单提交成功', 'success')
navigateBack()
navigateTo('/pages/shop-together/detail', { id: res.data.groupId })
}
onShow(() => {

View File

@@ -1,8 +1,13 @@
<script setup>
import { onLoad, onShow } from '@dcloudio/uni-app'
import { getProductDetail, getProductCommentList } from '@/api/mall'
import {
getProductDetail,
getProductCommentList,
getParticipateList
} from '@/api/mall'
import { ref, computed } from 'vue'
import { navigateTo } from '@/utils/router'
import { navigateTo, navigateBack } from '@/utils/router'
import { getRemainingTime, parseDateTime } from '../../utils/dateUtils'
const viewData = ref({})
const productId = ref('')
@@ -12,9 +17,31 @@
const commentNum = ref(0)
/** 分享弹窗 */
const shareDialog = ref(false)
/** 是否显示拼团数据 */
const isPingTuan = ref(false)
/** 拼团列表 */
const pingtuanList = ref([])
const getData = async productId => {
const res = await getProductDetail(productId)
viewData.value = res.data
try {
const res = await getProductDetail(productId)
viewData.value = res.data
if (res.data.groupActivities.length > 0) {
const c = await getParticipateList(res.data.groupActivities[0].id)
console.log(c.data, '====')
console.log(getRemainingTime('2026-01-28 23:46:40'))
pingtuanList.value = c.data.map(v => {
return {
...v,
showDate: true
}
})
isPingTuan.value = c.data.length > 0
} else {
isPingTuan.value = false
}
} catch (error) {
navigateBack()
}
}
/** 评论数量获取 */
@@ -36,14 +63,6 @@
return viewData.value.groupActivities[0].totalPeople
})
/**
* 顶部导航按钮点击事件
* @param index 0 返回 1 分享
*/
const onTopNav = index => {
console.log(index)
}
const onConfirm = () => {
navigateTo('/pages/mall/confirm-order', {
productId: productId.value,
@@ -82,11 +101,21 @@
</nav-bar>
<!-- 顶部图片 -->
<view class="top-img">
<image
:src="viewData.mainImage"
mode="scaleToFill"
class="img"
></image>
<swiper
class="swiper"
circular
indicator-dots
autoplay
:interval="4000"
:duration="500"
>
<swiper-item
v-for="(item, index) in viewData.imageGallery"
:key="index"
>
<image :src="item" mode="scaleToFill" class="img"></image>
</swiper-item>
</swiper>
</view>
<!-- 商品详情 -->
@@ -100,37 +129,64 @@
<text>拼单数量:{{ viewData.salesCount }}</text>
<text>好评率:99%</text>
</view>
<!-- 拼单量 -->
<view class="line-box">
<view class="left-img">
<text>拼单</text>
<image
src="/static/images/public/random1.png"
mode="scaleToFill"
class="avatar"
></image>
<image
src="/static/images/public/random2.png"
mode="scaleToFill"
class="avatar"
></image>
<image
src="/static/images/public/random3.png"
mode="scaleToFill"
class="avatar"
></image>
</view>
<text class="right-name">还需{{ getPeople }}人拼单</text>
</view>
<!-- 去拼团 -->
<!-- <view class="bottom-name">
<view class="count-down">
<text>拼单倒计时:</text>
<text>23:53:00</text>
</view>
<button>去拼单</button>
</view>-->
<view v-if="!groupId && isPingTuan" class="ping-box">
<view v-for="(item, index) in pingtuanList" :key="index">
<!-- 拼单量 -->
<view class="line-box">
<view class="left-img">
<text>拼单</text>
<image
src="/static/images/public/random1.png"
mode="scaleToFill"
class="avatar"
></image>
<image
src="/static/images/public/random2.png"
mode="scaleToFill"
class="avatar"
></image>
<image
src="/static/images/public/random3.png"
mode="scaleToFill"
class="avatar"
></image>
</view>
<text class="right-name">
还需{{ item.needPeople }}人拼单
</text>
</view>
<!-- 去拼团 -->
<view class="bottom-name">
<view class="count-down">
<text>拼单倒计时:</text>
<uni-countdown
:day="parseDateTime(item.endTime).day"
:hour="parseDateTime(item.endTime).hour"
:minute="parseDateTime(item.endTime).minute"
:second="parseDateTime(item.endTime).second"
:show-colon="false"
@timeup="
() => {
item.showDate = false
}
"
/>
</view>
<button
v-if="item.showDate"
@click="
navigateTo('/pages/mall/confirm-order', {
productId: productId,
groupId: item.id
})
"
>
去拼单
</button>
</view>
</view>
</view>
<!-- 评论入口 -->
<view
class="comment-box"
@@ -168,11 +224,17 @@
:id="productId"
:text="viewData.productName"
:cover="viewData.mainImage"
:price="viewData.minPrice"
></share-popup>
</view>
</template>
<style lang="scss" scoped>
.ping-box {
max-height: 500rpx;
overflow-y: auto;
}
.left-icon,
.right-icon {
height: 64rpx;
@@ -183,13 +245,17 @@
&::after {
content: '';
position: absolute;
bottom: 0;
bottom: -2rpx;
left: 0;
width: 100%;
height: 56rpx;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
}
.swiper {
width: 100%;
height: 628rpx;
}
.img {
width: 100%;
height: 628rpx;
@@ -197,9 +263,7 @@
}
.detail-box {
padding: 0 58rpx 150rpx;
font-family:
PingFang SC,
PingFang SC;
font-family: PingFang SC, PingFang SC;
font-style: normal;
text-transform: none;
.title {
@@ -257,7 +321,8 @@
}
.bottom-name {
display: flex;
justify-content: flex-end;
justify-content: space-between;
align-items: center;
font-weight: 500;
.count-down {
display: flex;
@@ -307,9 +372,7 @@
display: flex;
justify-content: space-between;
align-items: center;
font-family:
PingFang SC,
PingFang SC;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-style: normal;
text-transform: none;

View File

@@ -5,10 +5,10 @@
const { userInfo } = useAuthUser()
const inviteLink = computed(() => {
const { href } = window.location
return `${href}/pages/login/phone-register/phone-register?invitationCode=${userInfo.value.invitationCode}`
const { origin } = window.location
return `${origin}/pages/login/phone-register/phone-register?invitationCode=${userInfo.value.invitationCode}`
})
// 复制文本通用函数
const copyText = text => {
uni.setClipboardData({

View File

@@ -1,6 +1,6 @@
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { useUI } from '../../utils/use-ui'
const { showToast } = useUI()
@@ -9,13 +9,29 @@
const productId = ref('')
const qrcodeRef = ref(null)
const BASE_URL = computed(() => {
const { origin } = window.location
return `${origin}/pages/mall/detail?productId=${productId.value}&groupId=${groupId.value}`
})
// 复制文本通用函数
const copyText = text => {
uni.setClipboardData({
data: BASE_URL.value,
success: () => {
console.log('已复制到剪贴板')
},
fail: () => {
console.log('复制失败')
}
})
}
onLoad(e => {
groupId.value = e.id
productId.value = e.productId
// /pages/mall/detail
})
</script>
<template>
@@ -36,10 +52,60 @@
</view> -->
</view>
</view>
<!-- 复制连接 -->
<!-- #ifdef H5 -->
<view class="copy-box">
<text class="link">拼单链接:{{ BASE_URL }}</text>
<button @click="copyText">复制</button>
</view>
<!-- #endif -->
</view>
</template>
<style lang="scss" scoped>
.copy-box {
margin-top: 118rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.link {
// 超过宽度显示省略号
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 544rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
font-style: normal;
text-transform: none;
color: #00d993;
}
button {
margin-top: 48rpx;
width: 256rpx;
height: 64rpx;
line-height: 60rpx;
border-radius: 100rpx;
border: 2rpx solid #00d993;
color: #00d993;
padding: 0;
background: none;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 28rpx;
text-align: center;
font-style: normal;
text-transform: none;
&::after {
display: none !important;
}
}
}
.share-box {
height: 100vh;
width: 100vw;

View File

@@ -0,0 +1,30 @@
## 1.2.52025-04-14
- 修复 filterShow 导致的运行报错
## 1.2.42024-09-21
- 新增 支持控制显示位数 默认显示2位
## 1.2.32024-02-20
- 新增 支持控制小时分钟的显隐showHour showMinute
## 1.2.22022-01-19
- 修复 在微信小程序中样式不生效的bug
## 1.2.12022-01-18
- 新增 update 方法 ,在动态更新时间后,刷新组件
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
## 1.1.32021-10-18
- 重构
- 新增 font-size 支持自定义字体大小
## 1.1.22021-08-24
- 新增 支持国际化
## 1.1.12021-07-30
- 优化 vue3下小程序事件警告的问题
## 1.1.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.52021-06-18
- 修复 uni-countdown 重复赋值跳两秒的 bug
## 1.0.42021-05-12
- 新增 组件示例地址
## 1.0.32021-05-08
- 修复 uni-countdown 不能控制倒计时的 bug
## 1.0.22021-02-04
- 调整为uni_modules目录规范

View File

@@ -0,0 +1,6 @@
{
"uni-countdown.day": "day",
"uni-countdown.h": "h",
"uni-countdown.m": "m",
"uni-countdown.s": "s"
}

View File

@@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@@ -0,0 +1,6 @@
{
"uni-countdown.day": "天",
"uni-countdown.h": "时",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}

View File

@@ -0,0 +1,6 @@
{
"uni-countdown.day": "天",
"uni-countdown.h": "時",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}

View File

@@ -0,0 +1,278 @@
<template>
<view class="uni-countdown">
<text v-if="showDay" :style="[timeStyle]" class="uni-countdown__number">{{ d }}</text>
<text v-if="showDay" :style="[splitorStyle]" class="uni-countdown__splitor">{{dayText}}</text>
<text v-if="showHour" :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
<text v-if="showHour" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
<text v-if="showMinute" :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
<text v-if="showMinute" :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
<text :style="[timeStyle]" class="uni-countdown__number">{{ s }}</text>
<text v-if="!showColon" :style="[splitorStyle]" class="uni-countdown__splitor">{{secondText}}</text>
</view>
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const {
t
} = initVueI18n(messages)
/**
* Countdown 倒计时
* @description 倒计时组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=25
* @property {String} backgroundColor 背景色
* @property {String} color 文字颜色
* @property {Number} day 天数
* @property {Number} hour 小时
* @property {Number} minute 分钟
* @property {Number} second 秒
* @property {Number} timestamp 时间戳
* @property {Boolean} showDay = [true|false] 是否显示天数
* @property {Boolean} showHour = [true|false] 是否显示小时
* @property {Boolean} showMinute = [true|false] 是否显示分钟
* @property {Boolean} show-colon = [true|false] 是否以冒号为分隔符
* @property {String} splitorColor 分割符号颜色
* @event {Function} timeup 倒计时时间到触发事件
* @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
*/
export default {
name: 'UniCountdown',
emits: ['timeup'],
props: {
showDay: {
type: Boolean,
default: true
},
showHour: {
type: Boolean,
default: true
},
showMinute: {
type: Boolean,
default: true
},
showColon: {
type: Boolean,
default: true
},
start: {
type: Boolean,
default: true
},
backgroundColor: {
type: String,
default: ''
},
color: {
type: String,
default: '#333'
},
fontSize: {
type: Number,
default: 14
},
splitorColor: {
type: String,
default: '#333'
},
day: {
type: Number,
default: 0
},
hour: {
type: Number,
default: 0
},
minute: {
type: Number,
default: 0
},
second: {
type: Number,
default: 0
},
timestamp: {
type: Number,
default: 0
},
filterShow : {
type:Object,
default () {
return {}
}
}
},
data() {
return {
timer: null,
syncFlag: false,
d: '00',
h: '00',
i: '00',
s: '00',
leftTime: 0,
seconds: 0
}
},
computed: {
dayText() {
return t("uni-countdown.day")
},
hourText(val) {
return t("uni-countdown.h")
},
minuteText(val) {
return t("uni-countdown.m")
},
secondText(val) {
return t("uni-countdown.s")
},
timeStyle() {
const {
color,
backgroundColor,
fontSize
} = this
return {
color,
backgroundColor,
fontSize: `${fontSize}px`,
width: `${fontSize * 22 / 14}px`, // 按字体大小为 14px 时的比例缩放
lineHeight: `${fontSize * 20 / 14}px`,
borderRadius: `${fontSize * 3 / 14}px`,
}
},
splitorStyle() {
const { splitorColor, fontSize, backgroundColor } = this
return {
color: splitorColor,
fontSize: `${fontSize * 12 / 14}px`,
margin: backgroundColor ? `${fontSize * 4 / 14}px` : ''
}
}
},
watch: {
day(val) {
this.changeFlag()
},
hour(val) {
this.changeFlag()
},
minute(val) {
this.changeFlag()
},
second(val) {
this.changeFlag()
},
start: {
immediate: true,
handler(newVal, oldVal) {
if (newVal) {
this.startData();
} else {
if (!oldVal) return
clearInterval(this.timer)
}
}
}
},
created: function(e) {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
this.countDown()
},
// #ifndef VUE3
destroyed() {
clearInterval(this.timer)
},
// #endif
// #ifdef VUE3
unmounted() {
clearInterval(this.timer)
},
// #endif
methods: {
toSeconds(timestamp, day, hours, minutes, seconds) {
if (timestamp) {
return timestamp - parseInt(new Date().getTime() / 1000, 10)
}
return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
},
timeUp() {
clearInterval(this.timer)
this.$emit('timeup')
},
countDown() {
let seconds = this.seconds
let [day, hour, minute, second] = [0, 0, 0, 0]
if (seconds > 0) {
day = Math.floor(seconds / (60 * 60 * 24))
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
} else {
this.timeUp()
}
this.d = String(day).padStart(this.validFilterShow(this.filterShow.d), '0')
this.h = String(hour).padStart(this.validFilterShow(this.filterShow.h), '0')
this.i = String(minute).padStart(this.validFilterShow(this.filterShow.m), '0')
this.s = String(second).padStart(this.validFilterShow(this.filterShow.s), '0')
},
validFilterShow(filter){
return (filter && filter > 0) ? filter : 2;
},
startData() {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
if (this.seconds <= 0) {
this.seconds = this.toSeconds(0, 0, 0, 0, 0)
this.countDown()
return
}
clearInterval(this.timer)
this.countDown()
this.timer = setInterval(() => {
this.seconds--
if (this.seconds < 0) {
this.timeUp()
return
}
this.countDown()
}, 1000)
},
update(){
this.startData();
},
changeFlag() {
if (!this.syncFlag) {
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
this.startData();
this.syncFlag = true;
}
}
}
}
</script>
<style lang="scss" scoped>
$font-size: 14px;
.uni-countdown {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
&__splitor {
margin: 0 2px;
font-size: $font-size;
color: #333;
}
&__number {
border-radius: 3px;
text-align: center;
font-size: $font-size;
}
}
</style>

View File

@@ -0,0 +1,86 @@
{
"id": "uni-countdown",
"displayName": "uni-countdown 倒计时",
"version": "1.2.5",
"description": "CountDown 倒计时组件",
"keywords": [
"uni-ui",
"uniui",
"countdown",
"倒计时"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y",
"app-harmony": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
## CountDown 倒计时
> **组件名uni-countdown**
> 代码块: `uCountDown`
倒计时组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@@ -0,0 +1,12 @@
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-swiper-dot](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot)
## 1.1.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.62021-05-12
- 新增 示例地址
- 修复 示例项目缺少组件的Bug
## 1.0.52021-02-05
- 调整为uni_modules目录规范
- 新增 clickItem 事件,支持指示点控制轮播
- 新增 支持 pc 可用

View File

@@ -0,0 +1,218 @@
<template>
<view class="uni-swiper__warp">
<slot />
<view v-if="mode === 'default'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='default'>
<view v-for="(item,index) in info" @click="clickItem(index)" :style="{
'width': (index === current? dots.width*2:dots.width ) + 'px','height':dots.width/2 +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border-radius':'0px'}"
:key="index" class="uni-swiper__dots-item uni-swiper__dots-bar" />
</view>
<view v-if="mode === 'dot'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='dot'>
<view v-for="(item,index) in info" @click="clickItem(index)" :style="{
'width': dots.width + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
:key="index" class="uni-swiper__dots-item" />
</view>
<view v-if="mode === 'round'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='round'>
<view v-for="(item,index) in info" @click="clickItem(index)" :class="[index === current&&'uni-swiper__dots-long']" :style="{
'width':(index === current? dots.width*3:dots.width ) + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
:key="index" class="uni-swiper__dots-item " />
</view>
<view v-if="mode === 'nav'" key='nav' :style="{'background-color':dotsStyles.backgroundColor,'bottom':'0'}" class="uni-swiper__dots-box uni-swiper__dots-nav">
<text :style="{'color':dotsStyles.color}" class="uni-swiper__dots-nav-item">{{ (current+1)+"/"+info.length +' ' +info[current][field] }}</text>
</view>
<view v-if="mode === 'indexes'" key='indexes' :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box">
<view v-for="(item,index) in info" @click="clickItem(index)" :style="{
'width':dots.width + 'px','height':dots.height +'px' ,'color':index === current?dots.selectedColor:dots.color,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
:key="index" class="uni-swiper__dots-item uni-swiper__dots-indexes"><text class="uni-swiper__dots-indexes-text">{{ index+1 }}</text></view>
</view>
</view>
</template>
<script>
/**
* SwiperDod 轮播图指示点
* @description 自定义轮播图指示点
* @tutorial https://ext.dcloud.net.cn/plugin?id=284
* @property {Number} current 当前指示点索引,必须是通过 `swiper` 的 `change` 事件获取到的 `e.detail.current`
* @property {String} mode = [default|round|nav|indexes] 指示点的类型
* @value defualt 默认指示点
* @value round 圆形指示点
* @value nav 条形指示点
* @value indexes 索引指示点
* @property {String} field mode 为 nav 时显示的内容字段mode = nav 时必填)
* @property {String} info 轮播图的数据,通过数组长度决定指示点个数
* @property {Object} dotsStyles 指示点样式
* @event {Function} clickItem 组件触发点击事件时触发e={currentIndex}
*/
export default {
name: 'UniSwiperDot',
emits:['clickItem'],
props: {
info: {
type: Array,
default () {
return []
}
},
current: {
type: Number,
default: 0
},
dotsStyles: {
type: Object,
default () {
return {}
}
},
// 类型 default(默认) indexes long nav
mode: {
type: String,
default: 'default'
},
// 只在 nav 模式下生效,变量名称
field: {
type: String,
default: ''
}
},
data() {
return {
dots: {
width: 6,
height: 6,
bottom: 10,
color: '#fff',
backgroundColor: 'rgba(0, 0, 0, .3)',
border: '1px rgba(0, 0, 0, .3) solid',
selectedBackgroundColor: '#333',
selectedBorder: '1px rgba(0, 0, 0, .9) solid'
}
}
},
watch: {
dotsStyles(newVal) {
this.dots = Object.assign(this.dots, this.dotsStyles)
},
mode(newVal) {
if (newVal === 'indexes') {
this.dots.width = 14
this.dots.height = 14
} else {
this.dots.width = 6
this.dots.height = 6
}
}
},
created() {
if (this.mode === 'indexes') {
this.dots.width = 12
this.dots.height = 12
}
this.dots = Object.assign(this.dots, this.dotsStyles)
},
methods: {
clickItem(index) {
this.$emit('clickItem', index)
}
}
}
</script>
<style lang="scss" scoped>
.uni-swiper__warp {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: column;
position: relative;
overflow: hidden;
}
.uni-swiper__dots-box {
position: absolute;
bottom: 10px;
left: 0;
right: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
}
.uni-swiper__dots-item {
width: 8px;
border-radius: 100px;
margin-left: 6px;
background-color: rgba(0, 0, 0, 0.4);
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
/* #ifdef H5 */
// border-width: 5px 0;
// border-style: solid;
// border-color: transparent;
// background-clip: padding-box;
/* #endif */
// transition: width 0.2s linear; 不要取消注释,不然会不能变色
}
.uni-swiper__dots-item:first-child {
margin: 0;
}
.uni-swiper__dots-default {
border-radius: 100px;
}
.uni-swiper__dots-long {
border-radius: 50px;
}
.uni-swiper__dots-bar {
border-radius: 50px;
}
.uni-swiper__dots-nav {
bottom: 0px;
// height: 26px;
padding: 8px 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: flex-start;
align-items: center;
background-color: rgba(0, 0, 0, 0.2);
}
.uni-swiper__dots-nav-item {
/* overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; */
font-size: 14px;
color: #fff;
margin: 0 15px;
}
.uni-swiper__dots-indexes {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
// flex: 1;
justify-content: center;
align-items: center;
}
.uni-swiper__dots-indexes-text {
color: #fff;
font-size: 12px;
line-height: 14px;
}
</style>

View File

@@ -0,0 +1,87 @@
{
"id": "uni-swiper-dot",
"displayName": "uni-swiper-dot 轮播图指示点",
"version": "1.2.0",
"description": "自定义轮播图指示点组件",
"keywords": [
"uni-ui",
"uniui",
"轮播图指示点",
"dot",
"swiper"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
## SwiperDot 轮播图指示点
> **组件名uni-swiper-dot**
> 代码块: `uSwiperDot`
自定义轮播图指示点
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@@ -80,3 +80,53 @@ export function formatRelativeTime(timeStr) {
return timeStr.split(' ')[0] // 或使用更美观的格式
}
}
/**
* 获取天,时,分,秒
* @param {*} dateTimeStr
* @returns
*/
export const parseDateTime = dateTimeStr => {
const date = new Date(dateTimeStr)
// 检查日期是否有效
if (isNaN(date.getTime())) {
throw new Error('Invalid date string')
}
return {
day: date.getDate(), // 月份中的第几天1-31
hour: date.getHours(), // 小时0-23
minute: date.getMinutes(), // 分钟0-59
second: date.getSeconds() // 秒0-59
}
}
export const getRemainingTime = endTimeStr => {
const now = new Date().getTime() // 当前时间戳(毫秒)
const end = new Date(endTimeStr.replace(' ', 'T')).getTime() // 转为 ISO 格式并获取时间戳
if (isNaN(end)) {
throw new Error('无效的结束时间格式')
}
const diff = end - now // 剩余毫秒数
if (diff <= 0) {
return { day: 0, hour: 0, minute: 0, second: 0, isExpired: true }
}
const totalSeconds = Math.floor(diff / 1000)
const day = Math.floor(totalSeconds / (24 * 3600))
const hour = Math.floor((totalSeconds % (24 * 3600)) / 3600)
const minute = Math.floor((totalSeconds % 3600) / 60)
const second = totalSeconds % 60
return {
day,
hour,
minute,
second,
isExpired: false
}
}