parent
297b2ae453
commit
3e63234a7c
@ -0,0 +1,177 @@ |
||||
<template> |
||||
<page-meta :page-style="$theme.pageStyle"> |
||||
<!-- #ifndef H5 --> |
||||
<navigation-bar |
||||
:front-color="$theme.navColor" |
||||
:background-color="$theme.navBgColor" |
||||
/> |
||||
<!-- #endif --> |
||||
</page-meta> |
||||
<view class="h-full flex flex-col"> |
||||
<view class="flex-1 min-h-0 bg-white"> |
||||
<chat-scroll-view |
||||
v-model="chatList" |
||||
ref="chatRef" |
||||
:type="1" |
||||
:otherId="sessionActive" |
||||
:showAdd="true" |
||||
:safeAreaInsetBottom="false" |
||||
:avatar="appStore.getChatConfig.chat_logo" |
||||
bottom="100rpx" |
||||
> |
||||
<template #top> |
||||
<follow-official |
||||
:show="!!appStore.config.is_follow_official" |
||||
:title="appStore.getLoginConfig.involved_text" |
||||
/> |
||||
<session v-model="showPopup" /> |
||||
</template> |
||||
<template #empty> |
||||
<view class="w-full"> |
||||
<div |
||||
class="my-[60rpx] text-center text-[50rpx] font-medium" |
||||
> |
||||
{{ appStore.getChatConfig.chat_title }} |
||||
</div> |
||||
<problem-example |
||||
v-if="problem.length" |
||||
:data="problem" |
||||
@click-item="(value:any) => chatRef.sendLock(value)" |
||||
@show-more="showExamplePopup = true" |
||||
/> |
||||
|
||||
<view |
||||
v-if="indexData?.tips?.show" |
||||
class="bg-page flex flex-col items-center p-[30rpx] mt-[20rpx] m-[40rpx]" |
||||
> |
||||
<view class="flex items-center"> |
||||
<u-icon name="warning" :size="40" /> |
||||
<view class="text-lg ml-[10rpx] break-all">{{ |
||||
indexData?.tips?.title |
||||
}}</view> |
||||
</view> |
||||
<view |
||||
class="text-content text-sm mt-[20rpx] break-all" |
||||
> |
||||
{{ indexData?.tips?.sub_title }} |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
</chat-scroll-view> |
||||
<!-- 会话弹窗 --> |
||||
<session-popup v-model="showPopup" /> |
||||
<!-- 会话弹窗 --> |
||||
<problem-example-popup |
||||
v-model="showExamplePopup" |
||||
:data="problem" |
||||
@click-item="(value:any) => chatRef.sendLock(value)" |
||||
/> |
||||
<!-- 公告弹窗 --> |
||||
<notice-popup></notice-popup> |
||||
<!-- #ifdef MP --> |
||||
<!-- 微信小程序隐私弹窗 --> |
||||
<MpPrivacyPopup></MpPrivacyPopup> |
||||
<!-- #endif --> |
||||
<!-- #ifdef APP-PLUS --> |
||||
<!-- 苹果App隐私弹窗 --> |
||||
<IosPrivacyPopup |
||||
v-if="getClient() == ClientEnum.IOS" |
||||
@refresh="refreshHandler" |
||||
></IosPrivacyPopup> |
||||
<!-- #endif --> |
||||
</view> |
||||
<!-- <canvas canvas-id="canvasId" id="canvasId"></canvas> --> |
||||
<tabbar /> |
||||
</view> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { onHide, onPullDownRefresh, onShow } from '@dcloudio/uni-app' |
||||
import { reactive, ref, watch } from 'vue' |
||||
import { useAppStore } from '@/stores/app' |
||||
import { getDecorate } from '@/api/shop' |
||||
import { getSamplesLists } from '@/api/chat' |
||||
|
||||
import SessionPopup from './components/session-popup.vue' |
||||
import FollowOfficial from './components/follow-official.vue' |
||||
import NoticePopup from '@/components/notice-popup/notice-popup.vue' |
||||
import ProblemExample from './components/problem-example.vue' |
||||
import Session from './components/session.vue' |
||||
import ProblemExamplePopup from './components/problem-example-popup.vue' |
||||
// #ifdef MP |
||||
import MpPrivacyPopup from './components/mp-privacy-popup.vue' |
||||
// #endif |
||||
// #ifdef APP-PLUS |
||||
import { getClient } from '@/utils/client' |
||||
import { ClientEnum } from '@/enums/appEnums' |
||||
import IosPrivacyPopup from './components/ios-privacy-popup.vue' |
||||
// #endif |
||||
|
||||
import { shallowRef } from 'vue' |
||||
import { useSessionList } from './components/useSessionList' |
||||
import agreement from '@/components/agreement/agreement.vue' |
||||
|
||||
const { getSessionLists, sessionActive } = useSessionList() |
||||
const showPopup = ref(false) |
||||
const showExamplePopup = ref(false) |
||||
const problem = ref([]) |
||||
// 聊天记录列表 |
||||
const chatList = ref<any[]>([]) |
||||
const chatRef = shallowRef() |
||||
// 首页装修数据 |
||||
const indexData = reactive({ |
||||
tips: { |
||||
show: 0, |
||||
title: '', |
||||
sub_title: '' |
||||
} |
||||
}) |
||||
|
||||
// 海报实力 |
||||
const posterRef = shallowRef() |
||||
const modelKey = ref('') |
||||
const getProblemExample = async () => { |
||||
problem.value = await getSamplesLists() |
||||
} |
||||
|
||||
const getDecorateFunc = async () => { |
||||
try { |
||||
const { data } = await getDecorate({ id: 8 }) |
||||
indexData.tips = JSON.parse(data)[1]?.content |
||||
} catch (error) { |
||||
console.log('获取装修数据失败=>', error) |
||||
} |
||||
} |
||||
|
||||
const appStore = useAppStore() |
||||
const showChatView = ref(true) |
||||
|
||||
const refreshHandler = async () => { |
||||
await appStore.getConfig() |
||||
await getDecorateFunc() |
||||
await getSessionLists() |
||||
await getProblemExample() |
||||
} |
||||
|
||||
// onHide(() => { |
||||
// showChatView.value = false |
||||
// }) |
||||
|
||||
onShow(async () => { |
||||
getDecorateFunc() |
||||
getSessionLists() |
||||
getProblemExample() |
||||
}) |
||||
onPullDownRefresh(async () => { |
||||
refreshHandler() |
||||
uni.stopPullDownRefresh() |
||||
}) |
||||
</script> |
||||
|
||||
<style> |
||||
page { |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
@ -0,0 +1,55 @@ |
||||
<template> |
||||
<view |
||||
class="bg-primary flex px-[20rpx] items-center py-[16rpx]" |
||||
v-if="show && isShow" |
||||
> |
||||
<view class="flex-1 line-clamp-1 min-w-0 text-btn-text"> |
||||
{{ title }} |
||||
</view> |
||||
<router-navigate to="/packages/pages/follow_official/follow_official"> |
||||
<u-button |
||||
class="flex-none" |
||||
:plain="true" |
||||
type="primary" |
||||
size="medium" |
||||
shape="circle" |
||||
hover-class="none" |
||||
:custom-style="{ |
||||
height: '60rpx' |
||||
}" |
||||
> |
||||
前往关注 |
||||
</u-button> |
||||
</router-navigate> |
||||
|
||||
<view class="text-btn-text ml-[10rpx]" @click.stop="close"> |
||||
<u-icon name="close" /> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { IS_CLOSE_FOLLOW_OA } from '@/enums/constantEnums' |
||||
import cache from '@/utils/cache' |
||||
import { onBeforeMount } from 'vue' |
||||
import { ref } from 'vue' |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
title?: string |
||||
show?: boolean |
||||
}>(), |
||||
{ |
||||
title: '👉🏻关注公众号,下次访问不迷路', |
||||
show: false |
||||
} |
||||
) |
||||
const isShow = ref(false) |
||||
const close = () => { |
||||
cache.set(IS_CLOSE_FOLLOW_OA, true, 24 * 60 * 60) |
||||
isShow.value = false |
||||
} |
||||
|
||||
onBeforeMount(() => { |
||||
isShow.value = !cache.get(IS_CLOSE_FOLLOW_OA) |
||||
}) |
||||
</script> |
@ -0,0 +1,168 @@ |
||||
<template> |
||||
<!-- modal:隐私授权弹窗--> |
||||
<view v-if="show" class="modal-box" @tap.stop @touchmove.stop.prevent> |
||||
<view class="modal-mask"></view> |
||||
<view class="modal-dialog" @tap.stop> |
||||
<view class="title">服务协议及隐私政策</view> |
||||
<view class="content"> |
||||
为了更好的保障您的合法权益,请点击 |
||||
<text |
||||
class="text-[#101010]" |
||||
hover-class="hover" |
||||
@click=" |
||||
openAgreement( |
||||
'/packages/pages/agreement/agreement?type=service' |
||||
) |
||||
" |
||||
> |
||||
《用户协议》 |
||||
</text> |
||||
<text |
||||
class="text-[#101010]" |
||||
hover-class="hover" |
||||
@click=" |
||||
openAgreement( |
||||
'/packages/pages/agreement/agreement?type=privacy' |
||||
) |
||||
" |
||||
> |
||||
《隐私政策》 |
||||
</text> |
||||
并仔细阅读,如您同意全部内容,请点击同意开始使用我们的服务。 |
||||
</view> |
||||
<view class="btn-box"> |
||||
<button |
||||
class="btn disagree" |
||||
hover-class="hover" |
||||
@click="disagreePrivacy" |
||||
> |
||||
不同意 |
||||
</button> |
||||
<button |
||||
class="btn bg-primary text-white" |
||||
hover-class="hover" |
||||
@click="agreePrivacy" |
||||
> |
||||
同意并继续 |
||||
</button> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { nextTick, ref } from 'vue' |
||||
import cache from '@/utils/cache' |
||||
import { HAS_READ_PRIVACY } from '@/enums/constantEnums' |
||||
|
||||
const emit = defineEmits<{ |
||||
(event: 'refresh'): void |
||||
}>() |
||||
|
||||
const show = ref<boolean>(false) |
||||
|
||||
// 打开新页面 |
||||
const openAgreement = (url: string) => { |
||||
uni.navigateTo({ url }) |
||||
} |
||||
|
||||
// 不同意直接退出App |
||||
const disagreePrivacy = () => { |
||||
uni.$u.toast('同意隐私政策后继续使用') |
||||
} |
||||
|
||||
// 同意 |
||||
const agreePrivacy = async () => { |
||||
emit('refresh') |
||||
await nextTick() |
||||
show.value = false |
||||
cache.set(HAS_READ_PRIVACY, true) |
||||
} |
||||
|
||||
if (!cache.get(HAS_READ_PRIVACY)) show.value = true |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.modal-box { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
height: 100vh; |
||||
width: 100vw; |
||||
z-index: 9999; |
||||
background-color: #ffffff; |
||||
} |
||||
|
||||
.modal-box .modal-mask { |
||||
position: absolute; |
||||
z-index: 9999; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
background: rgba(0, 0, 0, 0.5); |
||||
} |
||||
|
||||
.modal-box .modal-dialog { |
||||
box-sizing: border-box; |
||||
position: absolute; |
||||
z-index: 99999; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
width: 80%; |
||||
padding: 40rpx 20rpx; |
||||
background: #ffffff; |
||||
border-radius: 20rpx; |
||||
} |
||||
|
||||
.modal-box .title { |
||||
text-align: center; |
||||
color: #333; |
||||
font-weight: bold; |
||||
font-size: 34rpx; |
||||
} |
||||
|
||||
.modal-box .content { |
||||
display: block; |
||||
font-size: 28rpx; |
||||
color: #666; |
||||
margin-top: 20rpx; |
||||
text-align: justify; |
||||
line-height: 1.6; |
||||
padding: 10rpx 20rpx; |
||||
} |
||||
|
||||
.modal-box .btn-box { |
||||
margin-top: 30rpx; |
||||
padding: 0 30rpx; |
||||
padding-bottom: 10rpx; |
||||
display: flex; |
||||
text-align: center; |
||||
} |
||||
|
||||
.modal-box .btn::after { |
||||
border: none; |
||||
display: none; |
||||
} |
||||
|
||||
.modal-box .btn-box .btn { |
||||
width: 50%; |
||||
height: 76rpx; |
||||
line-height: 76rpx; |
||||
margin: 0 10rpx; |
||||
padding: 0; |
||||
align-items: center; |
||||
justify-content: center; |
||||
border-radius: 60px; |
||||
font-size: 28rpx; |
||||
font-weight: 500; |
||||
} |
||||
|
||||
.modal-box .disagree { |
||||
color: #0f0f0f; |
||||
background: #f5f5f5; |
||||
} |
||||
</style> |
@ -0,0 +1,158 @@ |
||||
<template> |
||||
<!-- modal:隐私授权弹窗--> |
||||
<view v-if="show" class="modal-box" @tap.stop> |
||||
<view class="dialog" @tap.stop> |
||||
<view class="title">隐私政策提示</view> |
||||
<view class="content"> |
||||
欢迎使用{{ |
||||
appStore.getChatConfig.chat_title |
||||
}}小程序,请您在使用前点击 |
||||
<text |
||||
class="text-[#243245]" |
||||
hover-class="hover" |
||||
@click="openContract" |
||||
> |
||||
{{ name }} |
||||
</text> |
||||
并仔细阅读,如您同意全部内容,请点击同意开始使用我们的服务。 |
||||
</view> |
||||
<view class="btn-box"> |
||||
<button |
||||
class="btn disagree" |
||||
hover-class="hover" |
||||
@click="disagreePrivacy" |
||||
> |
||||
不同意 |
||||
</button> |
||||
<button |
||||
class="btn bg-primary text-white" |
||||
hover-class="hover" |
||||
id="agree-btn" |
||||
open-type="agreePrivacyAuthorization" |
||||
@agreeprivacyauthorization="agreePrivacy" |
||||
> |
||||
同意 |
||||
</button> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { ref } from 'vue' |
||||
import { useAppStore } from '@/stores/app' |
||||
const appStore = useAppStore() |
||||
|
||||
const name = ref<string>('') |
||||
const show = ref<boolean>(false) |
||||
|
||||
interface PrivacyRes { |
||||
errMsg: string |
||||
privacyContractName: string |
||||
needAuthorization: boolean |
||||
} |
||||
|
||||
if (wx.getPrivacySetting) { |
||||
wx.getPrivacySetting({ |
||||
success(res: PrivacyRes) { |
||||
console.log(res) |
||||
name.value = res.privacyContractName |
||||
show.value = res.needAuthorization |
||||
} |
||||
}) |
||||
} |
||||
|
||||
const openContract = () => { |
||||
wx.openPrivacyContract({ |
||||
success: () => {}, |
||||
fail: () => {} |
||||
}) |
||||
} |
||||
|
||||
const disagreeHandle = () => { |
||||
// 用户点击拒绝后 |
||||
show.value = false |
||||
} |
||||
|
||||
const disagreePrivacy = () => { |
||||
uni.$u.toast('同意隐私政策后可继续使用') |
||||
// wx.exitMiniProgram() |
||||
} |
||||
|
||||
const agreePrivacy = () => { |
||||
show.value = false |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.modal-box { |
||||
height: 100vh; |
||||
width: 100vw; |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
background: rgba(0, 0, 0, 0.6); |
||||
z-index: 99999; |
||||
} |
||||
|
||||
.modal-box .dialog { |
||||
box-sizing: border-box; |
||||
position: absolute; |
||||
bottom: 0; |
||||
width: 100%; |
||||
padding: 40rpx; |
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); |
||||
background: #ffffff; |
||||
border-radius: 20rpx 20rpx 0 0; |
||||
} |
||||
|
||||
.modal-box .title { |
||||
text-align: center; |
||||
color: #333; |
||||
font-weight: bold; |
||||
font-size: 34rpx; |
||||
} |
||||
|
||||
.modal-box .content { |
||||
display: block; |
||||
font-size: 28rpx; |
||||
color: #666; |
||||
margin-top: 20rpx; |
||||
text-align: justify; |
||||
line-height: 1.6; |
||||
padding: 10rpx 20rpx; |
||||
} |
||||
|
||||
.modal-box .btn-box { |
||||
margin-top: 50rpx; |
||||
padding: 0 30rpx; |
||||
padding-bottom: 30rpx; |
||||
display: flex; |
||||
text-align: center; |
||||
} |
||||
|
||||
.modal-box .btn::after { |
||||
border: none; |
||||
display: none; |
||||
} |
||||
|
||||
.modal-box .btn-box .btn { |
||||
width: 50%; |
||||
height: 76rpx; |
||||
line-height: 76rpx; |
||||
margin: 0 10rpx; |
||||
padding: 0; |
||||
align-items: center; |
||||
justify-content: center; |
||||
border-radius: 60px; |
||||
font-size: 28rpx; |
||||
font-weight: 500; |
||||
} |
||||
|
||||
.modal-box .disagree { |
||||
color: #0f0f0f; |
||||
background: #f5f5f5; |
||||
} |
||||
</style> |
@ -0,0 +1,89 @@ |
||||
<template> |
||||
<u-popup |
||||
v-model="show" |
||||
height="1000rpx" |
||||
mode="bottom" |
||||
safe-area-inset-bottom |
||||
border-radius="14" |
||||
closeable |
||||
> |
||||
<view class="py-[30rpx] h-full flex flex-col"> |
||||
<view class="text-2xl font-medium px-[30rpx]">问题示例</view> |
||||
<view> |
||||
<scroll-view :scroll-x="true" class="whitespace-nowrap"> |
||||
<view class="inline-block py-[20rpx]"> |
||||
<view class="flex px-[15rpx]"> |
||||
<view |
||||
class="mx-[15rpx] leading-[72rpx] px-[40rpx] bg-primary-light-9 text-primary rounded-[8rpx]" |
||||
:class="{ |
||||
'!text-btn-text !bg-primary': |
||||
currentTab == index |
||||
}" |
||||
v-for="(item, index) in data" |
||||
:key="item.id" |
||||
@click="currentTab = index" |
||||
> |
||||
<view>{{ item.name }}</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</scroll-view> |
||||
</view> |
||||
|
||||
<view |
||||
class="border-0 border-t border-light border-solid flex-1 min-h-0" |
||||
> |
||||
<scroll-view class="h-full" :scroll-y="true"> |
||||
<view class="px-[30rpx] py-[20rpx]"> |
||||
<template v-if="currentSample.length"> |
||||
<view |
||||
v-for="item in currentSample" |
||||
:key="item.id" |
||||
class="py-[10rpx]" |
||||
> |
||||
<view |
||||
class="text-center px-[20rpx] bg-page rounded py-[20rpx]" |
||||
@click="clickItem(item.content)" |
||||
> |
||||
{{ item.content }} |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<template v-else> |
||||
<view class="py-[150rpx]"> |
||||
<u-empty text="暂无数据" mode="data"></u-empty> |
||||
</view> |
||||
</template> |
||||
</view> |
||||
</scroll-view> |
||||
</view> |
||||
</view> |
||||
</u-popup> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { computed, ref } from 'vue' |
||||
|
||||
const props = defineProps<{ |
||||
data: any[] |
||||
modelValue: boolean |
||||
}>() |
||||
const emit = defineEmits<{ |
||||
(event: 'update:modelValue', value: boolean): void |
||||
(event: 'click-item', value: string): void |
||||
}>() |
||||
|
||||
const show = computed({ |
||||
get: () => { |
||||
return props.modelValue |
||||
}, |
||||
set: (value) => { |
||||
emit('update:modelValue', value) |
||||
} |
||||
}) |
||||
const currentTab = ref(0) |
||||
const currentSample = computed(() => props.data[currentTab.value]?.sample || []) |
||||
const clickItem = (value: string) => { |
||||
show.value = false |
||||
emit('click-item', value) |
||||
} |
||||
</script> |
@ -0,0 +1,76 @@ |
||||
<template> |
||||
<view class="problem-example px-[10rpx]"> |
||||
<view class="flex"> |
||||
<view |
||||
v-for="item in sliceData" |
||||
:key="item.id" |
||||
class="flex-1 mx-[10rpx]" |
||||
> |
||||
<view |
||||
class="flex flex-col justify-center items-center mb-[20rpx]" |
||||
> |
||||
<image |
||||
v-if="item.image" |
||||
class="w-[88rpx] h-[88rpx]" |
||||
:src="item.image" |
||||
alt="" |
||||
/> |
||||
<view |
||||
class="text-[30rpx] font-medium mt-[32rpx] line-clamp-1" |
||||
> |
||||
{{ item.name }} |
||||
</view> |
||||
</view> |
||||
<view> |
||||
<view |
||||
v-for="sample in slice3InData(item.sample)" |
||||
:key="sample.id" |
||||
class="bg-page mb-[20rpx] px-[20rpx] py-[15rpx] flex justify-center rounded-[12rpx] cursor-pointer" |
||||
@click="clickItem(sample.content)" |
||||
> |
||||
<view class="line-clamp-2 text-sm text-center"> |
||||
{{ sample.content }} |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view class="py-[10rpx]" @click="emit('show-more')" v-if="isShowMore"> |
||||
<view class="text-sm text-muted flex items-center justify-center"> |
||||
查看更多 |
||||
<u-icon name="arrow-right" size="26" /> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { computed } from 'vue' |
||||
|
||||
const props = defineProps<{ |
||||
data: any[] |
||||
}>() |
||||
const emit = defineEmits<{ |
||||
(event: 'click-item', value: string): void |
||||
(event: 'show-more'): void |
||||
}>() |
||||
const num = 3 |
||||
const slice3InData = (data: any[]) => { |
||||
return data.slice(0, num) |
||||
} |
||||
const sliceData = computed(() => { |
||||
return slice3InData(props.data) |
||||
}) |
||||
|
||||
const isShowMore = computed(() => { |
||||
if (props.data?.length > num) { |
||||
return true |
||||
} |
||||
if (props.data?.some((item) => item.sample.length > num)) { |
||||
return true |
||||
} |
||||
return false |
||||
}) |
||||
const clickItem = (value: string) => { |
||||
emit('click-item', value) |
||||
} |
||||
</script> |
@ -0,0 +1,102 @@ |
||||
<template> |
||||
<view |
||||
class="px-[20rpx] h-[80rpx] flex items-center rounded-[8rpx] mb-[20rpx] cursor-pointer" |
||||
:class="{ |
||||
' bg-page': isActive |
||||
}" |
||||
@click="activeId = itemId" |
||||
> |
||||
<u-icon name="chat" /> |
||||
|
||||
<view class="ml-[20rpx] flex-1 min-w-0"> |
||||
<u-input |
||||
height="50" |
||||
v-if="isEdit" |
||||
:custom-style="{ |
||||
fontSize: '26rpx' |
||||
}" |
||||
v-model="nameInput" |
||||
border |
||||
/> |
||||
<view v-else class="line-clamp-1">{{ name }}</view> |
||||
</view> |
||||
<template v-if="isActive"> |
||||
<template v-if="isEdit"> |
||||
<view |
||||
class="cursor-pointer mx-[12rpx] flex h-full" |
||||
@click="handleEdit" |
||||
> |
||||
<u-icon name="checkbox-mark" /> |
||||
</view> |
||||
<view |
||||
class="cursor-pointer flex h-full" |
||||
@click="isEdit = false" |
||||
> |
||||
<u-icon name="close" /> |
||||
</view> |
||||
</template> |
||||
<template v-else> |
||||
<view |
||||
class="cursor-pointer mr-[12rpx] flex h-full" |
||||
@click="isEdit = true" |
||||
> |
||||
<u-icon name="edit-pen" /> |
||||
</view> |
||||
<view |
||||
class="cursor-pointer flex h-full" |
||||
@click="emit('delete', itemId)" |
||||
> |
||||
<u-icon name="trash" /> |
||||
</view> |
||||
</template> |
||||
</template> |
||||
</view> |
||||
</template> |
||||
<script lang="ts" setup> |
||||
import { computed, ref, watch } from 'vue' |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
modelValue: number | string |
||||
name: string |
||||
itemId: number | string |
||||
}>(), |
||||
{} |
||||
) |
||||
const emit = defineEmits(['update:modelValue', 'edit', 'delete']) |
||||
|
||||
const nameInput = ref('') |
||||
const isEdit = ref(false) |
||||
|
||||
const activeId = computed({ |
||||
get() { |
||||
return props.modelValue |
||||
}, |
||||
set(value) { |
||||
emit('update:modelValue', value) |
||||
} |
||||
}) |
||||
|
||||
const isActive = computed(() => activeId.value === props.itemId) |
||||
|
||||
const handleEdit = () => { |
||||
isEdit.value = false |
||||
emit('edit', props.itemId, nameInput.value) |
||||
} |
||||
watch( |
||||
() => props.modelValue, |
||||
() => { |
||||
isEdit.value = false |
||||
} |
||||
) |
||||
|
||||
watch( |
||||
() => props.name, |
||||
(value) => { |
||||
nameInput.value = value |
||||
}, |
||||
{ |
||||
immediate: true |
||||
} |
||||
) |
||||
</script> |
@ -0,0 +1,90 @@ |
||||
<template> |
||||
<u-popup v-model="show" mode="left" width="480rpx" safe-area-inset-bottom> |
||||
<view class="flex flex-col h-full relative z-[9999]"> |
||||
<view class="px-[20rpx] py-[20rpx]"> |
||||
<u-button |
||||
type="primary" |
||||
:custom-style="{ width: '100%' }" |
||||
@click="sessionAddLock" |
||||
:loading="isLock" |
||||
> |
||||
+ 新建会话 |
||||
</u-button> |
||||
</view> |
||||
<view class="flex-1 min-h-0"> |
||||
<scroll-view scroll-y class="h-full"> |
||||
<view class="px-[20rpx]"> |
||||
<view v-for="item in sessionLists" :key="item.id"> |
||||
<session-item |
||||
v-model="sessionActive" |
||||
:item-id="item.id" |
||||
:name="item.name" |
||||
@edit="sessionEdit" |
||||
@delete="sessionDelete" |
||||
/> |
||||
</view> |
||||
</view> |
||||
</scroll-view> |
||||
</view> |
||||
<view class="px-[20rpx] py-[20rpx]"> |
||||
<u-button |
||||
:custom-style="{ width: '100%' }" |
||||
@click="sessionClear" |
||||
> |
||||
删除所有会话 |
||||
</u-button> |
||||
</view> |
||||
</view> |
||||
</u-popup> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { computed } from 'vue' |
||||
import { useAppStore } from '@/stores/app' |
||||
import { useSessionList } from './useSessionList' |
||||
import { useLockFn } from '@/hooks/useLockFn' |
||||
import SessionItem from './session-item.vue' |
||||
import { watch } from 'vue' |
||||
const props = defineProps({ |
||||
modelValue: { |
||||
type: Boolean |
||||
} |
||||
}) |
||||
const emit = defineEmits<{ |
||||
(event: 'update:modelValue', value: boolean): void |
||||
}>() |
||||
const show = computed({ |
||||
get: () => { |
||||
return props.modelValue |
||||
}, |
||||
set: (value) => { |
||||
emit('update:modelValue', value) |
||||
} |
||||
}) |
||||
const { |
||||
sessionLists, |
||||
sessionAdd, |
||||
sessionEdit, |
||||
sessionDelete, |
||||
sessionClear, |
||||
sessionActive |
||||
} = useSessionList() |
||||
|
||||
const { lockFn: sessionAddLock, isLock } = useLockFn(async () => { |
||||
await sessionAdd() |
||||
}) |
||||
|
||||
const appStore = useAppStore() |
||||
watch( |
||||
() => appStore.getChatConfig, |
||||
() => { |
||||
if (appStore.getChatConfig.is_reopen) { |
||||
sessionAddLock() |
||||
appStore.getChatConfig.is_reopen = 0 |
||||
} |
||||
}, |
||||
{ |
||||
immediate: true |
||||
} |
||||
) |
||||
</script> |
@ -0,0 +1,31 @@ |
||||
<template> |
||||
<view class="bg-[#333] text-white flex items-center px-[20rpx] py-[24rpx]"> |
||||
<u-icon name="list" :size="40" @click="show = true" /> |
||||
<view class="flex-1 line-clamp-1 ml-[20rpx]">{{ currentSession }}</view> |
||||
<view class="text-xs flex items-center" @click="sessionAdd"> |
||||
<u-icon name="plus-circle" class="mr-[8rpx]" size="36" /> |
||||
新建 |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import { computed } from 'vue' |
||||
import { useSessionList } from './useSessionList' |
||||
const props = defineProps({ |
||||
modelValue: { |
||||
type: Boolean |
||||
} |
||||
}) |
||||
const emit = defineEmits<{ |
||||
(event: 'update:modelValue', value: boolean): void |
||||
}>() |
||||
const show = computed({ |
||||
get: () => { |
||||
return props.modelValue |
||||
}, |
||||
set: (value) => { |
||||
emit('update:modelValue', value) |
||||
} |
||||
}) |
||||
const { currentSession, sessionAdd } = useSessionList() |
||||
</script> |
@ -0,0 +1,81 @@ |
||||
import { |
||||
chatCategoryAdd, |
||||
chatCategoryClear, |
||||
chatCategoryDelete, |
||||
chatCategoryEdit, |
||||
getChatCategoryLists |
||||
} from '@/api/chat' |
||||
import { computed, ref } from 'vue' |
||||
const sessionActive = ref(0) |
||||
const sessionLists = ref<any[]>([]) |
||||
export function useSessionList() { |
||||
const getSessionLists = async () => { |
||||
try { |
||||
const data = await getChatCategoryLists({ page_type: 0 }) |
||||
sessionLists.value = data?.lists || [] |
||||
if (sessionActive.value === 0) { |
||||
initSessionActive() |
||||
} |
||||
return sessionLists.value |
||||
} catch (error) { |
||||
sessionLists.value = [] |
||||
initSessionActive() |
||||
} |
||||
} |
||||
|
||||
const sessionAdd = async () => { |
||||
await chatCategoryAdd({}) |
||||
await getSessionLists() |
||||
initSessionActive() |
||||
} |
||||
|
||||
const sessionDelete = async (id: number) => { |
||||
const res = await uni.showModal({ |
||||
title: '确定删除该会话?' |
||||
}) |
||||
if (res.confirm) { |
||||
await chatCategoryDelete({ id }) |
||||
await getSessionLists() |
||||
initSessionActive() |
||||
} |
||||
} |
||||
|
||||
const sessionClear = async () => { |
||||
const res = await uni.showModal({ |
||||
title: '确定清除所有会话?' |
||||
}) |
||||
if (res.confirm) { |
||||
await chatCategoryClear() |
||||
await getSessionLists() |
||||
initSessionActive() |
||||
} |
||||
} |
||||
|
||||
const sessionEdit = async (id: number, name: string) => { |
||||
await chatCategoryEdit({ id, name }) |
||||
getSessionLists() |
||||
} |
||||
|
||||
const initSessionActive = () => { |
||||
sessionActive.value = sessionLists.value[0]?.id || 0 |
||||
} |
||||
|
||||
const currentSession = computed(() => { |
||||
return ( |
||||
sessionLists.value.find( |
||||
(session) => session.id === sessionActive.value |
||||
)?.name || '' |
||||
) |
||||
}) |
||||
|
||||
return { |
||||
getSessionLists, |
||||
sessionActive, |
||||
sessionLists, |
||||
sessionAdd, |
||||
sessionEdit, |
||||
sessionDelete, |
||||
sessionClear, |
||||
currentSession |
||||
} |
||||
} |
@ -0,0 +1,176 @@ |
||||
<template> |
||||
<page-meta :page-style="$theme.pageStyle"> |
||||
<!-- #ifndef H5 --> |
||||
<navigation-bar |
||||
:front-color="$theme.navColor" |
||||
:background-color="$theme.navBgColor" |
||||
/> |
||||
<!-- #endif --> |
||||
</page-meta> |
||||
<view class="h-full flex flex-col"> |
||||
<view class="flex-1 min-h-0 bg-white"> |
||||
<chat-scroll-view |
||||
v-model="chatList" |
||||
ref="chatRef" |
||||
:type="1" |
||||
:otherId="sessionActive" |
||||
:showAdd="true" |
||||
:safeAreaInsetBottom="false" |
||||
:avatar="appStore.getChatConfig.chat_logo" |
||||
bottom="100rpx" |
||||
> |
||||
<template #top> |
||||
<follow-official |
||||
:show="!!appStore.config.is_follow_official" |
||||
:title="appStore.getLoginConfig.involved_text" |
||||
/> |
||||
<session v-model="showPopup" /> |
||||
</template> |
||||
<template #empty> |
||||
<view class="w-full"> |
||||
<div |
||||
class="my-[60rpx] text-center text-[50rpx] font-medium" |
||||
> |
||||
{{ appStore.getChatConfig.chat_title }} |
||||
</div> |
||||
<problem-example |
||||
v-if="problem.length" |
||||
:data="problem" |
||||
@click-item="(value:any) => chatRef.sendLock(value)" |
||||
@show-more="showExamplePopup = true" |
||||
/> |
||||
|
||||
<view |
||||
v-if="indexData?.tips?.show" |
||||
class="bg-page flex flex-col items-center p-[30rpx] mt-[20rpx] m-[40rpx]" |
||||
> |
||||
<view class="flex items-center"> |
||||
<u-icon name="warning" :size="40" /> |
||||
<view class="text-lg ml-[10rpx] break-all">{{ |
||||
indexData?.tips?.title |
||||
}}</view> |
||||
</view> |
||||
<view |
||||
class="text-content text-sm mt-[20rpx] break-all" |
||||
> |
||||
{{ indexData?.tips?.sub_title }} |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
</chat-scroll-view> |
||||
<!-- 会话弹窗 --> |
||||
<session-popup v-model="showPopup" /> |
||||
<!-- 会话弹窗 --> |
||||
<problem-example-popup |
||||
v-model="showExamplePopup" |
||||
:data="problem" |
||||
@click-item="(value:any) => chatRef.sendLock(value)" |
||||
/> |
||||
<!-- 公告弹窗 --> |
||||
<notice-popup></notice-popup> |
||||
<!-- #ifdef MP --> |
||||
<!-- 微信小程序隐私弹窗 --> |
||||
<MpPrivacyPopup></MpPrivacyPopup> |
||||
<!-- #endif --> |
||||
<!-- #ifdef APP-PLUS --> |
||||
<!-- 苹果App隐私弹窗 --> |
||||
<IosPrivacyPopup |
||||
v-if="getClient() == ClientEnum.IOS" |
||||
@refresh="refreshHandler" |
||||
></IosPrivacyPopup> |
||||
<!-- #endif --> |
||||
</view> |
||||
<!-- <canvas canvas-id="canvasId" id="canvasId"></canvas> --> |
||||
<tabbar /> |
||||
</view> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { onHide, onPullDownRefresh, onShow } from '@dcloudio/uni-app' |
||||
import { reactive, ref, watch } from 'vue' |
||||
import { useAppStore } from '@/stores/app' |
||||
import { getDecorate } from '@/api/shop' |
||||
import { getSamplesLists } from '@/api/chat' |
||||
|
||||
import SessionPopup from './components/session-popup.vue' |
||||
import FollowOfficial from './components/follow-official.vue' |
||||
import NoticePopup from '@/components/notice-popup/notice-popup.vue' |
||||
import ProblemExample from './components/problem-example.vue' |
||||
import Session from './components/session.vue' |
||||
import ProblemExamplePopup from './components/problem-example-popup.vue' |
||||
// #ifdef MP |
||||
import MpPrivacyPopup from './components/mp-privacy-popup.vue' |
||||
// #endif |
||||
// #ifdef APP-PLUS |
||||
import { getClient } from '@/utils/client' |
||||
import { ClientEnum } from '@/enums/appEnums' |
||||
import IosPrivacyPopup from './components/ios-privacy-popup.vue' |
||||
// #endif |
||||
|
||||
import { shallowRef } from 'vue' |
||||
import { useSessionList } from './components/useSessionList' |
||||
import agreement from '@/components/agreement/agreement.vue' |
||||
|
||||
const { getSessionLists, sessionActive } = useSessionList() |
||||
const showPopup = ref(false) |
||||
const showExamplePopup = ref(false) |
||||
const problem = ref([]) |
||||
// 聊天记录列表 |
||||
const chatList = ref<any[]>([]) |
||||
const chatRef = shallowRef() |
||||
// 首页装修数据 |
||||
const indexData = reactive({ |
||||
tips: { |
||||
show: 0, |
||||
title: '', |
||||
sub_title: '' |
||||
} |
||||
}) |
||||
// 海报实力 |
||||
const posterRef = shallowRef() |
||||
const modelKey = ref('') |
||||
const getProblemExample = async () => { |
||||
problem.value = await getSamplesLists() |
||||
} |
||||
|
||||
const getDecorateFunc = async () => { |
||||
try { |
||||
const { data } = await getDecorate({ id: 8 }) |
||||
indexData.tips = JSON.parse(data)[1]?.content |
||||
} catch (error) { |
||||
console.log('获取装修数据失败=>', error) |
||||
} |
||||
} |
||||
|
||||
const appStore = useAppStore() |
||||
const showChatView = ref(true) |
||||
|
||||
const refreshHandler = async () => { |
||||
await appStore.getConfig() |
||||
await getDecorateFunc() |
||||
await getSessionLists() |
||||
await getProblemExample() |
||||
} |
||||
|
||||
// onHide(() => { |
||||
// showChatView.value = false |
||||
// }) |
||||
|
||||
onShow(async () => { |
||||
getDecorateFunc() |
||||
getSessionLists() |
||||
getProblemExample() |
||||
}) |
||||
onPullDownRefresh(async () => { |
||||
refreshHandler() |
||||
uni.stopPullDownRefresh() |
||||
}) |
||||
</script> |
||||
|
||||
<style> |
||||
page { |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
</style> |
After Width: | Height: | Size: 83 KiB |
Loading…
Reference in new issue