chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view
|
||||
class="flex-1 h-full"
|
||||
scroll-y="true"
|
||||
@scrolltolower="handleLoadMore"
|
||||
:lower-threshold="50"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="handleRefresh"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<template v-if="recentAgentList?.length">
|
||||
<recent-used-agent-item
|
||||
v-for="info in recentAgentList"
|
||||
:key="info.id"
|
||||
:icon="info.icon"
|
||||
:name="info.name"
|
||||
:description="info.description"
|
||||
:modifiedTime="formatTimeAgo(info.modified)"
|
||||
:showModifiedTime="isLoggedIn"
|
||||
:paymentRequired="info.paymentRequired && enableSubscription === 1"
|
||||
:subscribed="info.subscribed"
|
||||
@click="handleAgentClick(info)"
|
||||
@longpress="isLoggedIn ? handleDeleteUserUsedAgent(info) : () => {}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- loading状态或刷新状态时隐藏空状态 -->
|
||||
<template v-else-if="!loading && !refreshing">
|
||||
<view class="empty-state h-full">
|
||||
<image
|
||||
:src="noData"
|
||||
class="empty-image"
|
||||
:alt="t('Mobile.Common.noDataImageAlt')"
|
||||
/>
|
||||
<text class="empty-text">{{ t("Mobile.Common.noData") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 刷新状态时隐藏加载状态 -->
|
||||
<template v-else-if="!refreshing">
|
||||
<view class="loading-state">
|
||||
<text class="loading-text">{{ t("Mobile.Page.loadingMore") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态时隐藏没有更多数据状态 -->
|
||||
<template v-if="showNoMore">
|
||||
<view class="no-more-state">
|
||||
<text class="no-more-text">{{ t("Mobile.Common.noMoreData") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
||||
import type { AgentInfo } from "@/types/interfaces/agent";
|
||||
import {
|
||||
apiUserUsedAgentList,
|
||||
apiUserUsedAgentDelete,
|
||||
} from "@/servers/agentDev";
|
||||
import { apiPublishedAgentList } from "@/servers/square";
|
||||
import type {
|
||||
SquarePublishedListParams,
|
||||
SquarePublishedItemInfo,
|
||||
} from "@/types/interfaces/square";
|
||||
import type { Page, RequestResponse } from "@/types/interfaces/request";
|
||||
import RecentUsedAgentItem from "@/components/recent-used-agent-item/recent-used-agent-item.uvue";
|
||||
import noData from "@/static/assets/no_data.png";
|
||||
import { formatTimeAgo } from "@/utils/common";
|
||||
import { USER_NO_LOGIN, REDIRECT_LOGIN } from "@/constants/codes.constants";
|
||||
import { AgentComponentTypeEnum } from "@/types/enums/agent";
|
||||
import { useI18n } from "@/utils/i18n";
|
||||
import { apiTenantConfig } from "@/servers/account";
|
||||
import { TENANT_CONFIG_INFO } from "@/constants/home.constants";
|
||||
import type { TenantConfigInfo } from "@/types/interfaces/login";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// Props 定义
|
||||
interface Props {
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
// Emits 定义 - 只保留需要父组件处理的事件
|
||||
const emit = defineEmits<{
|
||||
(e: "agent-click", info: AgentInfo): void;
|
||||
}>();
|
||||
|
||||
// 最近使用智能体列表
|
||||
const recentAgentList = ref<AgentInfo[]>([]);
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
// 刷新状态
|
||||
const refreshing = ref<boolean>(false);
|
||||
// 是否还有更多
|
||||
const hasMore = ref<boolean>(false);
|
||||
// 当前页码
|
||||
const currentPage = ref<number>(1);
|
||||
|
||||
// 租户信息
|
||||
const tenantConfigInfo = ref<TenantConfigInfo | null>(null);
|
||||
|
||||
// 租户订阅限制强受控标志
|
||||
const enableSubscription = computed((): number => {
|
||||
return tenantConfigInfo.value?.enableSubscription ?? 0;
|
||||
});
|
||||
|
||||
// 是否显示没有更多数据
|
||||
const showNoMore = computed(() => {
|
||||
return (
|
||||
!loading.value && !hasMore.value && recentAgentList.value?.length > 8
|
||||
);
|
||||
});
|
||||
|
||||
// 查询用户最近使用过的智能体列表
|
||||
const fetchUserUsedAgentList = async (
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
) => {
|
||||
try {
|
||||
const res = await apiUserUsedAgentList({
|
||||
pageIndex: page,
|
||||
size: pageSize,
|
||||
});
|
||||
loading.value = false;
|
||||
const { code, data } = res || {};
|
||||
if (code === SUCCESS_CODE) {
|
||||
const newList = data || [];
|
||||
if (page > 1) {
|
||||
// 追加数据
|
||||
const existingList = recentAgentList.value || [];
|
||||
recentAgentList.value = [...existingList, ...newList];
|
||||
} else {
|
||||
// 替换数据
|
||||
recentAgentList.value = newList;
|
||||
}
|
||||
|
||||
// 是否有更多数据, 如果数据长度大于0,则有更多数据
|
||||
hasMore.value = newList?.length > 0;
|
||||
currentPage.value = page;
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取公开的智能体列表(未登录时使用)
|
||||
const fetchPublishedAgentList = async () => {
|
||||
try {
|
||||
const params: SquarePublishedListParams = {
|
||||
targetType: AgentComponentTypeEnum.Agent,
|
||||
targetSubType: "ChatBot",
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
category: "",
|
||||
kw: "",
|
||||
};
|
||||
const res = await apiPublishedAgentList(params);
|
||||
loading.value = false;
|
||||
const { code, data } = res as unknown as RequestResponse<
|
||||
Page<SquarePublishedItemInfo>
|
||||
>;
|
||||
if (code === SUCCESS_CODE) {
|
||||
const newList = data.records || [];
|
||||
// 转换为 AgentInfo 格式
|
||||
const agentList: AgentInfo[] = newList.map(
|
||||
(item: SquarePublishedItemInfo) =>
|
||||
({
|
||||
id: item.id,
|
||||
agentId: item.targetId,
|
||||
name: item.name,
|
||||
icon: item.icon,
|
||||
description: item.description,
|
||||
agentType: item.agentType,
|
||||
paymentRequired: item.paymentRequired,
|
||||
subscribed: item.subscribed,
|
||||
}) as AgentInfo,
|
||||
);
|
||||
recentAgentList.value = agentList;
|
||||
// 只显示第一页,所以没有更多数据
|
||||
hasMore.value = false;
|
||||
currentPage.value = 1;
|
||||
return agentList;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除最近使用的智能体
|
||||
const handleDeleteUserUsedAgent = (info: AgentInfo) => {
|
||||
uni.showModal({
|
||||
title: t("Mobile.AgentList.confirmDeleteAgent"),
|
||||
content: info?.name,
|
||||
success: (res) => {
|
||||
// 确认删除
|
||||
if (res.confirm) {
|
||||
deleteUserUsedAgent(info?.agentId);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 删除用户最近智能体使用记录
|
||||
const deleteUserUsedAgent = async (agentId: number) => {
|
||||
const res = await apiUserUsedAgentDelete(agentId);
|
||||
const { code, message } = res || {};
|
||||
if (code === SUCCESS_CODE) {
|
||||
recentAgentList.value = recentAgentList.value.filter(
|
||||
(item) => item.agentId !== agentId,
|
||||
);
|
||||
} else if (code !== USER_NO_LOGIN && code !== REDIRECT_LOGIN) {
|
||||
uni.showToast({
|
||||
title: message || t("Mobile.AgentList.deleteFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
if (refreshing.value) {
|
||||
return;
|
||||
}
|
||||
refreshing.value = true;
|
||||
currentPage.value = 1;
|
||||
hasMore.value = true;
|
||||
// 根据是否登录调用不同的 API
|
||||
const fetchPromise = props.isLoggedIn
|
||||
? fetchUserUsedAgentList()
|
||||
: fetchPublishedAgentList();
|
||||
fetchPromise.finally(() => {
|
||||
// 添加延迟,让loading效果更明显
|
||||
setTimeout(() => {
|
||||
refreshing.value = false;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
if (hasMore.value && !loading.value) {
|
||||
// 下一页页码
|
||||
const nextPage = currentPage.value + 1;
|
||||
fetchUserUsedAgentList(nextPage);
|
||||
}
|
||||
};
|
||||
|
||||
// 点击智能体 - 发射事件给父组件处理
|
||||
const handleAgentClick = (info: AgentInfo) => {
|
||||
emit("agent-click", info);
|
||||
};
|
||||
|
||||
// 获取租户配置
|
||||
const fetchTenantConfig = async () => {
|
||||
const { code, data } = await apiTenantConfig();
|
||||
if (code === SUCCESS_CODE) {
|
||||
tenantConfigInfo.value = data;
|
||||
// 缓存租户信息
|
||||
uni.setStorageSync(TENANT_CONFIG_INFO, JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化租户配置数据
|
||||
const initTenantConfig = async () => {
|
||||
// 1. 首先尝试从本地缓存读取
|
||||
const tenantConfigInfoString = await uni.getStorageSync(TENANT_CONFIG_INFO);
|
||||
if (tenantConfigInfoString) {
|
||||
tenantConfigInfo.value = JSON.parse(tenantConfigInfoString);
|
||||
}
|
||||
// 2. 然后必须发起网络强同步请求,以获得最新的 enableSubscription 状态
|
||||
await fetchTenantConfig();
|
||||
};
|
||||
|
||||
// 初始化加载数据
|
||||
const loadData = async () => {
|
||||
loading.value = true;
|
||||
// 强同步更新最新的租户配置,防止进入首页列表时计费状态渲染不正确
|
||||
await initTenantConfig();
|
||||
const list = props.isLoggedIn
|
||||
? await fetchUserUsedAgentList(1, 20 * currentPage.value)
|
||||
: await fetchPublishedAgentList();
|
||||
};
|
||||
|
||||
onMounted(()=>{
|
||||
loadData();
|
||||
})
|
||||
|
||||
onPageShow(()=>{
|
||||
loadData();
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
loadData,
|
||||
recentAgentList,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 空状态 */
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.empty-image {
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 4rpx solid #e8e8e8;
|
||||
border-top: 4rpx solid #8b5cf6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 没有更多数据状态 */
|
||||
.no-more-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,502 @@
|
||||
<template>
|
||||
<view class="conversation-list h-full">
|
||||
<scroll-view
|
||||
class="h-full"
|
||||
scroll-y
|
||||
@scrolltolower="handleLoadMore"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="handleRefresh"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<!-- 会话列表 -->
|
||||
<template v-if="conversationList?.length">
|
||||
<view class="list-wrapper">
|
||||
<view
|
||||
class="conversation-item"
|
||||
v-for="item in conversationList"
|
||||
:key="item.id"
|
||||
@click="handleItemClick(item)"
|
||||
@longpress="handleLongPress(item)"
|
||||
>
|
||||
<view class="item-content">
|
||||
<view class="title-row">
|
||||
<text class="conversation-title">{{
|
||||
item.topic || t("Mobile.Conversation.unnamed")
|
||||
}}</text>
|
||||
<text class="time-label">{{
|
||||
formatTimeAgo(item.modified)
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="bottom-row">
|
||||
<text class="agent-name">{{
|
||||
item.agent?.name || t("Mobile.Conversation.unknownAgent")
|
||||
}}</text>
|
||||
<view
|
||||
v-if="item.taskStatus === TaskStatus.EXECUTING"
|
||||
class="status-tag"
|
||||
>
|
||||
<text class="status-text">{{
|
||||
t("Mobile.Conversation.executing")
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- loading状态或刷新状态时隐藏空状态 -->
|
||||
<template v-else-if="!loading && !refreshing">
|
||||
<view class="empty-state h-full">
|
||||
<image
|
||||
:src="noData"
|
||||
class="empty-image"
|
||||
:alt="t('Mobile.Common.noDataImageAlt')"
|
||||
/>
|
||||
<text class="empty-text">{{ t("Mobile.Common.noData") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 刷新状态时隐藏加载状态 -->
|
||||
<template v-else-if="!refreshing">
|
||||
<view class="loading-state">
|
||||
<text class="loading-text">{{ t("Mobile.Page.loadingMore") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态时隐藏没有更多数据状态 -->
|
||||
<template v-if="showNoMore">
|
||||
<view class="no-more-state">
|
||||
<text class="no-more-text">{{ t("Mobile.Common.noMoreData") }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import {
|
||||
apiAgentConversationList,
|
||||
apiAgentConversationUpdate,
|
||||
apiAgentConversationDelete,
|
||||
} from "@/servers/conversation";
|
||||
import type {
|
||||
ConversationListParams,
|
||||
ConversationInfo,
|
||||
} from "@/types/interfaces/conversationInfo";
|
||||
import type { AgentConversationUpdateParams } from "@/types/interfaces/agent";
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
||||
import { formatTimeAgo } from "@/utils/common";
|
||||
import noData from "@/static/assets/no_data.png";
|
||||
import { TaskStatus } from "@/types/enums/agent";
|
||||
import { useI18n } from "@/utils/i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// Props 定义
|
||||
interface Props {
|
||||
isLoggedIn?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isLoggedIn: false,
|
||||
});
|
||||
|
||||
// Emits 定义
|
||||
const emit = defineEmits<{
|
||||
(e: "conversation-click", item: ConversationInfo): void;
|
||||
}>();
|
||||
|
||||
// 会话列表
|
||||
const conversationList = ref<ConversationInfo[]>([]);
|
||||
// 加载状态
|
||||
const loading = ref<boolean>(false);
|
||||
// 刷新状态
|
||||
const refreshing = ref<boolean>(false);
|
||||
// 是否还有更多
|
||||
const hasMore = ref<boolean>(false);
|
||||
// 最后一条记录ID
|
||||
const lastId = ref<number | null>(null);
|
||||
|
||||
// 是否显示没有更多数据
|
||||
const showNoMore = computed(() => {
|
||||
return (
|
||||
!loading.value && !hasMore.value && conversationList.value?.length > 8
|
||||
);
|
||||
});
|
||||
|
||||
// 获取会话列表
|
||||
const fetchConversationList = async (isLoadMore: boolean = false) => {
|
||||
if (!props.isLoggedIn) {
|
||||
loading.value = false;
|
||||
refreshing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const params: ConversationListParams = {
|
||||
agentId: null,
|
||||
lastId: isLoadMore ? lastId.value : null,
|
||||
limit: 20,
|
||||
};
|
||||
|
||||
const res = await apiAgentConversationList(params);
|
||||
loading.value = false;
|
||||
|
||||
const { code, data } = res || {};
|
||||
if (code === SUCCESS_CODE) {
|
||||
const newList = (data || []) as ConversationInfo[];
|
||||
|
||||
if (isLoadMore) {
|
||||
// 追加数据
|
||||
conversationList.value = [...conversationList.value, ...newList];
|
||||
} else {
|
||||
// 替换数据
|
||||
conversationList.value = newList;
|
||||
}
|
||||
|
||||
// 更新lastId和hasMore
|
||||
if (data && data.length > 0) {
|
||||
lastId.value = data[data.length - 1].id;
|
||||
hasMore.value = newList.length >= 20;
|
||||
} else {
|
||||
hasMore.value = false;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
if (refreshing.value) {
|
||||
return;
|
||||
}
|
||||
refreshing.value = true;
|
||||
lastId.value = null;
|
||||
hasMore.value = true;
|
||||
|
||||
await fetchConversationList(false);
|
||||
|
||||
setTimeout(() => {
|
||||
refreshing.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
if (hasMore.value && !loading.value) {
|
||||
loading.value = true;
|
||||
fetchConversationList(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 点击会话项
|
||||
const handleItemClick = (item: ConversationInfo) => {
|
||||
emit("conversation-click", item);
|
||||
};
|
||||
|
||||
/**
|
||||
* 长按会话项
|
||||
*/
|
||||
const handleLongPress = (item: ConversationInfo) => {
|
||||
const options = [
|
||||
t("Mobile.Components.HistoryConversationList.rename"),
|
||||
t("Mobile.Components.HistoryConversationList.delete"),
|
||||
];
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: options,
|
||||
cancelText: t("Mobile.Common.cancel"),
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
handleRename(item);
|
||||
} else if (res.tapIndex === 1) {
|
||||
handleDelete(item);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改名称
|
||||
*/
|
||||
const handleRename = (item: ConversationInfo) => {
|
||||
uni.showModal({
|
||||
title: t("Mobile.Components.HistoryConversationList.renameModalTitle"),
|
||||
editable: true,
|
||||
placeholderText: t(
|
||||
"Mobile.Components.HistoryConversationList.renamePlaceholder",
|
||||
),
|
||||
content: item.topic || "",
|
||||
cancelText: t("Mobile.Common.cancel"),
|
||||
confirmText: t("Mobile.Common.confirm"),
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const newName = res.content?.trim();
|
||||
if (!newName) {
|
||||
uni.showToast({
|
||||
title: t(
|
||||
"Mobile.Components.HistoryConversationList.renameTitleEmpty",
|
||||
),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (newName.length > 50) {
|
||||
uni.showToast({
|
||||
title: t(
|
||||
"Mobile.Components.HistoryConversationList.renameTitleTooLong",
|
||||
),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const params: AgentConversationUpdateParams = {
|
||||
id: item.id,
|
||||
topic: newName,
|
||||
};
|
||||
const updateRes = await apiAgentConversationUpdate(params);
|
||||
if (updateRes.code === SUCCESS_CODE) {
|
||||
uni.showToast({
|
||||
title: t(
|
||||
"Mobile.Components.HistoryConversationList.renameSuccess",
|
||||
),
|
||||
icon: "success",
|
||||
});
|
||||
// 刷新当前项显示
|
||||
item.topic = newName;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: updateRes.message || t("Mobile.Common.operationFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Common.operationFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*/
|
||||
const handleDelete = (item: ConversationInfo) => {
|
||||
uni.showModal({
|
||||
title: t("Mobile.Components.HistoryConversationList.deleteModalTitle"),
|
||||
content: t(
|
||||
"Mobile.Components.HistoryConversationList.deleteModalContent",
|
||||
),
|
||||
cancelText: t("Mobile.Common.cancel"),
|
||||
confirmText: t("Mobile.Common.confirm"),
|
||||
confirmColor: "#ff4d4f",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const deleteRes = await apiAgentConversationDelete(item.id);
|
||||
if (deleteRes.code === SUCCESS_CODE) {
|
||||
uni.showToast({
|
||||
title: t(
|
||||
"Mobile.Components.HistoryConversationList.deleteSuccess",
|
||||
),
|
||||
icon: "success",
|
||||
});
|
||||
// 从列表中移除
|
||||
conversationList.value = conversationList.value.filter(
|
||||
(c) => c.id !== item.id,
|
||||
);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: deleteRes.message || t("Mobile.Common.operationFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Common.operationFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化加载数据
|
||||
const loadData = async () => {
|
||||
loading.value = true;
|
||||
await fetchConversationList(false);
|
||||
};
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
loadData,
|
||||
conversationList,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.conversation-list {
|
||||
background: #f5f5f5;
|
||||
|
||||
.list-wrapper {
|
||||
padding: 0;
|
||||
|
||||
.conversation-item {
|
||||
background: #fff;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
padding: 32rpx 24rpx;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.title-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.conversation-title {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 42rpx;
|
||||
margin-right: 16rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.agent-name {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
flex-shrink: 0;
|
||||
// background: #e6f7ff;
|
||||
// border: 1rpx solid #F0F0F0;
|
||||
// border-radius: 4rpx;
|
||||
padding: 4rpx 0rpx;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.status-text {
|
||||
font-size: 22rpx;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
line-height: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.empty-image {
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 4rpx solid #e8e8e8;
|
||||
border-top: 4rpx solid #8b5cf6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 没有更多数据状态 */
|
||||
.no-more-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
269
qiming-mobile/pages/index/header-menu/header-menu.uvue
Normal file
269
qiming-mobile/pages/index/header-menu/header-menu.uvue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<x-dropdown v-if="userInfo" ref="dropdown" @open="checkSubscriptionEnable">
|
||||
<view class="trigger">
|
||||
<image
|
||||
:src="currentAvatar"
|
||||
mode="aspectFill"
|
||||
class="avatar"
|
||||
:alt="t('Mobile.Common.avatarAlt')"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<text class="iconfont icon-a-Chevrondown icon-down"></text>
|
||||
</view>
|
||||
|
||||
<template #menu>
|
||||
<view class="custom-menu-wrapper">
|
||||
<view class="custom-menu">
|
||||
<view class="flex flex-col user-info-box">
|
||||
<!-- 用户名 -->
|
||||
<text class="user-label text-ellipsis">{{
|
||||
userInfo.nickName || userInfo.userName
|
||||
}}</text>
|
||||
<!-- 手机号或邮箱 -->
|
||||
<text class="user-name text-ellipsis">{{
|
||||
userInfo.phone || userInfo.email
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 我的订阅 -->
|
||||
<view
|
||||
v-if="isSubscriptionEnabled"
|
||||
class="custom-menu-item"
|
||||
hover-class="custom-menu-item-active"
|
||||
@click="handleMySubscriptions"
|
||||
>
|
||||
<text class="iconfont icon-stars"></text>
|
||||
<text class="menu-text">{{
|
||||
t("Mobile.Header.mySubscriptions")
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 个人资料 -->
|
||||
<view
|
||||
class="custom-menu-item"
|
||||
hover-class="custom-menu-item-active"
|
||||
@click="handlePersonalInfo"
|
||||
>
|
||||
<text class="iconfont icon-User"></text>
|
||||
<text class="menu-text">{{ t("Mobile.Header.profile") }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view
|
||||
class="custom-menu-item"
|
||||
hover-class="custom-menu-item-active"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<text class="iconfont icon-Exit"></text>
|
||||
<text class="menu-text">{{ t("Mobile.Header.logout") }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</x-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import type { MenuListItem } from "@/types/interfaces/common";
|
||||
import type { UserInfo, TenantConfigInfo } from "@/types/interfaces/login";
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
||||
import { apiLogout, apiTenantConfig } from "@/servers/account";
|
||||
import defaultAvatar from "@/static/assets/avatar.png";
|
||||
import { ACCESS_TOKEN, TENANT_CONFIG_INFO } from "@/constants/home.constants";
|
||||
import { useI18n } from "@/utils/i18n";
|
||||
|
||||
const { t, loadI18n } = useI18n();
|
||||
|
||||
interface Props {
|
||||
userInfo: UserInfo;
|
||||
}
|
||||
|
||||
// 接收组件属性,设置默认值
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
userInfo: null,
|
||||
});
|
||||
|
||||
// 当前显示的头像,默认使用传入的 avatar
|
||||
const currentAvatar = ref<string>(defaultAvatar);
|
||||
|
||||
// 新增 ref
|
||||
const dropdown = ref<any>(null);
|
||||
|
||||
// 是否开启订阅
|
||||
const isSubscriptionEnabled = ref<boolean>(false);
|
||||
|
||||
const checkSubscriptionEnable = async () => {
|
||||
const tenantConfigInfoString = uni.getStorageSync(TENANT_CONFIG_INFO);
|
||||
if (tenantConfigInfoString) {
|
||||
try {
|
||||
const tenantConfig = JSON.parse(
|
||||
tenantConfigInfoString,
|
||||
) as TenantConfigInfo;
|
||||
isSubscriptionEnabled.value = tenantConfig.enableSubscription === 1;
|
||||
} catch (error) {
|
||||
console.error("解析租户配置失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await apiTenantConfig();
|
||||
if (res.code === SUCCESS_CODE && res.data != null) {
|
||||
const latestConfig = res.data!;
|
||||
isSubscriptionEnabled.value = latestConfig.enableSubscription === 1;
|
||||
uni.setStorageSync(TENANT_CONFIG_INFO, JSON.stringify(latestConfig));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("同步最新租户配置失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听 userInfo 变化,更新当前头像和订阅状态
|
||||
watch(
|
||||
() => props.userInfo,
|
||||
(newUserInfo) => {
|
||||
currentAvatar.value = newUserInfo?.avatar || defaultAvatar;
|
||||
checkSubscriptionEnable();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const handlePersonalInfo = () => {
|
||||
uni.navigateTo({
|
||||
url: "/subpackages/pages/about-me/about-me",
|
||||
});
|
||||
};
|
||||
|
||||
const handleMySubscriptions = () => {
|
||||
uni.navigateTo({
|
||||
url: "/subpackages/pages/my-subscriptions/my-subscriptions",
|
||||
});
|
||||
};
|
||||
|
||||
// 图片加载错误时触发,切换为默认图片
|
||||
const handleImageError = () => {
|
||||
currentAvatar.value = defaultAvatar;
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const result = await apiLogout();
|
||||
if (result.code === SUCCESS_CODE) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Header.logoutSuccess"),
|
||||
icon: "success",
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.setStorageSync(ACCESS_TOKEN, ""); // 清空token【兼容鸿蒙系统】
|
||||
uni.clearStorageSync(); // 清空所有缓存
|
||||
globalThis.appConfig.redirectUrl = null;
|
||||
// 获取一次语言
|
||||
loadI18n(false);
|
||||
|
||||
// #ifdef H5 || WEB
|
||||
uni.reLaunch({ url: "/subpackages/pages/login/login" });
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.reLaunch({ url: "/subpackages/pages/login-weixin/login-weixin" });
|
||||
// #endif
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Header.logoutFailed"),
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露关闭方法
|
||||
defineExpose({
|
||||
closeDropdown: () => {
|
||||
if (dropdown.value) {
|
||||
dropdown.value.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.trigger {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
|
||||
.avatar {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-down {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-menu-wrapper {
|
||||
.custom-menu {
|
||||
width: 320rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid rgba(81, 71, 255, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
.user-info-box {
|
||||
gap: 12rpx;
|
||||
padding: 28rpx 20rpx 20rpx 20rpx;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||
|
||||
.user-label {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 24rpx 20rpx;
|
||||
gap: 12rpx;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&-active {
|
||||
background: rgba(81, 71, 255, 0.05);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
340
qiming-mobile/pages/index/index.uvue
Normal file
340
qiming-mobile/pages/index/index.uvue
Normal file
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<view class="h-full flex flex-col border-b">
|
||||
<!-- 顶部导航栏 -->
|
||||
<custom-nav-bar :title="t('Mobile.Nav.home')" className="border-b">
|
||||
<template v-slot:left>
|
||||
<view class="flex flex-row items-center gap-4">
|
||||
<header-menu
|
||||
v-if="isLoggedIn"
|
||||
:userInfo="userInfo"
|
||||
ref="headerMenuRef"
|
||||
/>
|
||||
<view v-else class="login-btn-wrapper" @click="handleLogin">
|
||||
<text class="login-btn-text">{{
|
||||
t("Mobile.Common.loginRegister")
|
||||
}}</text>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="icon-search-box" @click="handleSearch">
|
||||
<text class="iconfont iconfont icon-Search font-48"></text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- #ifdef H5 || WEB -->
|
||||
<template v-slot:right>
|
||||
<view class="icon-search-box" @click="handleSearch">
|
||||
<text class="iconfont iconfont icon-Search font-48"></text>
|
||||
</view>
|
||||
</template>
|
||||
<!-- #endif -->
|
||||
</custom-nav-bar>
|
||||
<uni-notice-bar
|
||||
v-if="!isLoggedIn"
|
||||
single
|
||||
show-close
|
||||
show-icon
|
||||
:text="t('Mobile.Common.noticeExperience')"
|
||||
></uni-notice-bar>
|
||||
|
||||
<!-- 未登录时只显示智能体列表,不显示标签页 -->
|
||||
<agent-list-content
|
||||
v-if="!isLoggedIn"
|
||||
class="h-full w-full"
|
||||
ref="agentListContentRef"
|
||||
:is-logged-in="isLoggedIn"
|
||||
@agent-click="handleAgentClick"
|
||||
/>
|
||||
|
||||
<!-- 登录后显示标签页:最近使用 + 会话记录 -->
|
||||
<pane-tabs
|
||||
v-if="isLoggedIn"
|
||||
:key="currentLang"
|
||||
v-model="activeTab"
|
||||
:lazy-load="false"
|
||||
with-bottom-padding
|
||||
@change="handleTabChange"
|
||||
>
|
||||
<pane-tab key-value="recent" tab="Mobile.Common.recentUsed">
|
||||
<agent-list-content
|
||||
class="h-full w-full"
|
||||
ref="agentListContentRef"
|
||||
:is-logged-in="isLoggedIn"
|
||||
@agent-click="handleAgentClickLast"
|
||||
/>
|
||||
</pane-tab>
|
||||
|
||||
<pane-tab
|
||||
key-value="history"
|
||||
tab="Mobile.Common.conversationHistory"
|
||||
>
|
||||
<conversation-list-content
|
||||
class="h-full w-full"
|
||||
ref="conversationListContentRef"
|
||||
:is-logged-in="isLoggedIn"
|
||||
@conversation-click="handleConversationClick"
|
||||
/>
|
||||
</pane-tab>
|
||||
</pane-tabs>
|
||||
|
||||
<!-- 登录弹窗 -->
|
||||
<auth-login-popup ref="loginPopupRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
||||
import { apiUserInfo } from "@/servers/account";
|
||||
import type { AgentInfo } from "@/types/interfaces/agent";
|
||||
import HeaderMenu from "./header-menu/header-menu.uvue";
|
||||
import AgentListContent from "./agent-list-content/agent-list-content.uvue";
|
||||
import ConversationListContent from "./conversation-list-content/conversation-list-content.uvue";
|
||||
import type { UserInfo } from "@/types/interfaces/login";
|
||||
import { apiTenantConfig } from "@/servers/account";
|
||||
import { onAddToFavorites } from "@dcloudio/uni-app";
|
||||
import {
|
||||
getCurrentPagePath,
|
||||
jumpToAgentDetailPage,
|
||||
} from "@/utils/commonBusiness";
|
||||
import AuthLoginPopup from "@/components/auth-login-popup/auth-login-popup.uvue";
|
||||
import { useAuthInterceptor } from "@/hooks/useAuthInterceptor";
|
||||
import { getCurrentPageFullPath } from "@/utils/common";
|
||||
import { setCurrentPageNavigationBarTitle } from "@/utils/system";
|
||||
import PaneTabs from "@/components/pane-tabs/pane-tabs.uvue";
|
||||
import PaneTab from "@/components/pane-tabs/pane-tab.uvue";
|
||||
import { TENANT_CONFIG_INFO } from "@/constants/home.constants";
|
||||
import { useI18n, applyTabBarI18n } from "@/utils/i18n";
|
||||
|
||||
const { t, syncLanguageFromUser } = useI18n();
|
||||
|
||||
const activeTab = ref("recent");
|
||||
// 分享标题
|
||||
const shareTitle = ref("");
|
||||
const fallbackShareTitle = computed(() => t("Mobile.Nav.home"));
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
if (key === "recent" && agentListContentRef.value) {
|
||||
// 切换到最近使用时加载数据
|
||||
agentListContentRef.value.loadData();
|
||||
} else if (key === "history" && conversationListContentRef.value) {
|
||||
// 切换到会话记录时加载数据
|
||||
conversationListContentRef.value.loadData();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理会话项点击
|
||||
const handleConversationClick = (item: any) => {
|
||||
// 跳转到智能体详情页面,并传递消息数据
|
||||
jumpToAgentDetailPage(item.agentId, item.id);
|
||||
};
|
||||
|
||||
// 头部菜单ref
|
||||
const headerMenuRef = ref<any>(null);
|
||||
// 智能体列表组件ref
|
||||
const agentListContentRef = ref<any>(null);
|
||||
// 会话列表组件ref
|
||||
const conversationListContentRef = ref<any>(null);
|
||||
// 使用登录拦截 composable
|
||||
const {
|
||||
loginPopupRef,
|
||||
handleAgentClick: baseHandleAgentClick,
|
||||
checkAuthAndShowPopup,
|
||||
hasToken,
|
||||
} = useAuthInterceptor();
|
||||
|
||||
// 包装 handleAgentClick 以适配 AgentInfo 类型
|
||||
const handleAgentClick = (info: AgentInfo) => {
|
||||
baseHandleAgentClick({
|
||||
agentId: info.agentId,
|
||||
name: info.name,
|
||||
agentType: info.agentType,
|
||||
icon: info.icon, // 跳转临时会话页面时使用
|
||||
description: info.description, // 跳转临时会话页面时使用
|
||||
});
|
||||
};
|
||||
|
||||
// 最近使用
|
||||
const handleAgentClickLast = (info: AgentInfo) => {
|
||||
const { lastConversationId, agentId, name, agentType } = info || {};
|
||||
if (lastConversationId) {
|
||||
jumpToAgentDetailPage(agentId, lastConversationId);
|
||||
} else {
|
||||
baseHandleAgentClick({
|
||||
agentId: agentId,
|
||||
name: name,
|
||||
agentType: agentType,
|
||||
icon: info.icon, // 跳转临时会话页面时使用
|
||||
description: info.description, // 跳转临时会话页面时使用
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理登录点击
|
||||
const handleLogin = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (loginPopupRef.value) {
|
||||
loginPopupRef.value.open();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5 || WEB
|
||||
const currentUrl = getCurrentPageFullPath();
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/subpackages/pages/login/login?redirect=" +
|
||||
encodeURIComponent(currentUrl),
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<UserInfo>();
|
||||
|
||||
// 判断是否登录
|
||||
const isLoggedIn = ref<boolean>(false);
|
||||
|
||||
// 查询当前登录用户信息
|
||||
const fetchUserInfo = async () => {
|
||||
const res = await apiUserInfo();
|
||||
const { code, data } = res || {};
|
||||
if (code === SUCCESS_CODE) {
|
||||
userInfo.value = data;
|
||||
await syncLanguageFromUser(data?.lang || "");
|
||||
}
|
||||
};
|
||||
|
||||
// 设置分享标题
|
||||
const setCurrentShareTitle = async () => {
|
||||
// 获取本地用户配置
|
||||
const tenantConfigInfoString = await uni.getStorageSync(TENANT_CONFIG_INFO);
|
||||
if (tenantConfigInfoString) {
|
||||
try {
|
||||
const { siteName, siteDescription } = JSON.parse(
|
||||
tenantConfigInfoString,
|
||||
);
|
||||
|
||||
const titleList = [];
|
||||
if (siteName) {
|
||||
titleList.push(siteName);
|
||||
}
|
||||
if (siteDescription) {
|
||||
titleList.push(siteDescription);
|
||||
}
|
||||
shareTitle.value = titleList.join(" ");
|
||||
} catch (error) {
|
||||
console.error("获取本地用户配置失败:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onLoad((options: { cId?: string; fileProxyUrl?: string }) => {
|
||||
// 检测是否为文件预览分享跳转
|
||||
// #ifdef MP-WEIXIN
|
||||
if (options?.cId && options?.fileProxyUrl) {
|
||||
// 跳转到文件预览页面
|
||||
// 注意:fileProxyUrl 在分享时已经被 encodeURIComponent 编码过了
|
||||
// 这里不需要再次编码,直接传递即可
|
||||
uni.navigateTo({
|
||||
url: `/subpackages/pages/file-preview-page/file-preview-page?cId=${options.cId}&fileProxyUrl=${encodeURIComponent(options.fileProxyUrl)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 设置当前页面导航栏标题
|
||||
setCurrentPageNavigationBarTitle();
|
||||
|
||||
// 设置分享标题
|
||||
setCurrentShareTitle();
|
||||
});
|
||||
|
||||
onPageShow(async () => {
|
||||
// 确保每次回到首页都刷新一次 TabBar 翻译,
|
||||
// 解决语言切换重载后 TabBar 对象可能未就绪导致 uni.setTabBarItem 失效的问题。
|
||||
applyTabBarI18n();
|
||||
|
||||
const loggedIn = await hasToken();
|
||||
isLoggedIn.value = loggedIn || false;
|
||||
// 如果是登录状态,需要等待 pane-tabs 内的组件初始化
|
||||
if (isLoggedIn.value) {
|
||||
await nextTick();
|
||||
fetchUserInfo();
|
||||
}
|
||||
});
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
// 检查登录状态,如果未登录则显示登录弹窗
|
||||
if (!checkAuthAndShowPopup()) {
|
||||
return;
|
||||
}
|
||||
const currentUrl = getCurrentPagePath();
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/subpackages/pages/agent-search/agent-search?type=Home&backUrl=" +
|
||||
encodeURIComponent(currentUrl),
|
||||
});
|
||||
};
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 转发给朋友
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: shareTitle.value || fallbackShareTitle.value,
|
||||
path: "/pages/index/index",
|
||||
};
|
||||
});
|
||||
|
||||
// 收藏
|
||||
onAddToFavorites(() => {
|
||||
return {
|
||||
title: shareTitle.value || fallbackShareTitle.value,
|
||||
};
|
||||
});
|
||||
// #endif
|
||||
|
||||
onHide(() => {
|
||||
if (headerMenuRef.value) {
|
||||
headerMenuRef.value.closeDropdown();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.login-btn-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15rpx 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 100rpx;
|
||||
margin-right: 8rpx;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-btn-text {
|
||||
color: #333333;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user