Merge pull request #2082 from xinnan-tech/fix_mobile

fix:修复mcp地址显示慢,增加服务端地址校验
This commit is contained in:
hrz
2025-08-20 16:27:06 +08:00
committed by GitHub
2 changed files with 97 additions and 30 deletions
+22 -6
View File
@@ -28,6 +28,11 @@ const agentId = computed(() => pluginStore.currentAgentId)
const mcpAddress = ref('') const mcpAddress = ref('')
const mcpTools = ref<string[]>([]) const mcpTools = ref<string[]>([])
// 初始化时从本地存储加载MCP地址
if (uni.getStorageSync('cachedMcpAddress_' + agentId.value)) {
mcpAddress.value = uni.getStorageSync('cachedMcpAddress_' + agentId.value)
}
// 参数编辑相关 // 参数编辑相关
const showParamDialog = ref(false) const showParamDialog = ref(false)
const currentFunction = ref<any>(null) const currentFunction = ref<any>(null)
@@ -56,12 +61,23 @@ async function mergeFunctions() {
) )
if (agentId.value) { if (agentId.value) {
const [address, tools] = await Promise.all([ // 优先获取并显示MCP地址
getMcpAddress(agentId.value), try {
getMcpTools(agentId.value), const address = await getMcpAddress(agentId.value)
]) mcpAddress.value = address
mcpAddress.value = address // 缓存到本地存储,下次打开页面可以立即显示
mcpTools.value = tools || [] uni.setStorageSync('cachedMcpAddress_' + agentId.value, address)
} catch (error) {
console.error('获取MCP地址失败:', error)
}
// 异步获取MCP工具列表,不阻塞UI显示
try {
const tools = await getMcpTools(agentId.value)
mcpTools.value = tools || []
} catch (error) {
console.error('获取MCP工具列表失败:', error)
}
} }
} }
@@ -27,6 +27,7 @@ const cacheInfo = reactive({
// 服务端地址设置 // 服务端地址设置
const baseUrlInput = ref('') const baseUrlInput = ref('')
const urlError = ref('')
// 系统信息(保留) // 系统信息(保留)
const systemInfo = computed(() => { const systemInfo = computed(() => {
@@ -52,10 +53,58 @@ function getCacheInfo() {
} }
} }
// 验证URL格式
function validateUrl() {
urlError.value = ''
if (!baseUrlInput.value) {
return
}
if (!/^https?:\/\/.+\/xiaozhi$/.test(baseUrlInput.value)) {
urlError.value = '请输入有效的服务端地址(以 http 或 https 开头,并以 /xiaozhi 结尾)'
}
}
// 测试服务端地址
async function testServerBaseUrl() {
// 先清除错误信息
urlError.value = ''
if (!baseUrlInput.value || !/^https?:\/\/.+\/xiaozhi$/.test(baseUrlInput.value)) {
return false
}
try {
const response = await uni.request({
url: `${baseUrlInput.value}/api/ping`,
method: 'GET',
timeout: 3000
})
if (response.statusCode === 200) {
return true
} else {
toast.error('无效地址,请检查服务端是否启动或网络连接是否正常')
return false
}
} catch (error) {
console.error('测试服务端地址失败:', error)
toast.error('无效地址,请检查服务端是否启动或网络连接是否正常')
return false
}
}
// 保存服务端地址 // 保存服务端地址
function saveServerBaseUrl() { async function saveServerBaseUrl() {
if (!baseUrlInput.value || !/^https?:\/\//.test(baseUrlInput.value)) { if (!baseUrlInput.value || !/^https?:\/\/.+\/xiaozhi$/.test(baseUrlInput.value)) {
toast.warning('请输入有效的服务端地址(以 http 或 https 开头)') toast.warning('请输入有效的服务端地址(以 http 或 https 开头,并以 /xiaozhi 结尾')
return
}
// 测试地址有效性
const isServerValid = await testServerBaseUrl()
if (!isServerValid) {
return return
} }
setServerBaseUrlOverride(baseUrlInput.value) setServerBaseUrlOverride(baseUrlInput.value)
@@ -64,20 +113,20 @@ function saveServerBaseUrl() {
clearAllCacheAfterUrlChange() clearAllCacheAfterUrlChange()
uni.showModal({ uni.showModal({
title: '重启应用', title: '重启应用',
content: '服务端地址已保存并清空缓存,是否立即重启生效?', content: '服务端地址已保存并清空缓存,是否立即重启生效?',
confirmText: '立即重启', confirmText: '立即重启',
cancelText: '稍后', cancelText: '稍后',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
restartApp() restartApp()
} }
else { else {
toast.success('已保存,可稍后手动重启应用') toast.success('已保存,可稍后手动重启应用')
} }
}, },
}) })
} }
// 重置为 env 默认 // 重置为 env 默认
function resetServerBaseUrl() { function resetServerBaseUrl() {
@@ -173,7 +222,7 @@ function showAbout() {
title: `关于${import.meta.env.VITE_APP_TITLE}`, title: `关于${import.meta.env.VITE_APP_TITLE}`,
content: `${import.meta.env.VITE_APP_TITLE}\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server`, content: `${import.meta.env.VITE_APP_TITLE}\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server`,
title: `关于小智智控台`, title: `关于小智智控台`,
content: `小智智控台\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智智控台ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server 0.7.5`, content: `小智智控台\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server 0.7.5`,
showCancel: false, showCancel: false,
confirmText: '确定', confirmText: '确定',
}) })
@@ -201,7 +250,6 @@ onMounted(async () => {
</text> </text>
</view> </view>
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx] overflow-hidden" <view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx] overflow-hidden"
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"> style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
<view class="mb-[24rpx]"> <view class="mb-[24rpx]">
@@ -214,20 +262,22 @@ onMounted(async () => {
</view> </view>
<view class="mb-[24rpx]"> <view class="mb-[24rpx]">
<input v-model="baseUrlInput"
class="h-[88rpx] w-full border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] px-[24rpx] text-[28rpx] text-[#232338] transition-all focus:border-[#336cff] focus:bg-white placeholder:text-[#9d9ea3] focus:shadow-[0_0_0_4rpx_rgba(51,108,255,0.1)]"
type="text" placeholder="输入服务端地址,如 https://example.com/api">
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden"> <view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
<wd-input <wd-input
v-model="baseUrlInput" v-model="baseUrlInput"
type="text" type="text"
clearable clearable
:maxlength="200" :maxlength="200"
placeholder="输入服务端地址,如 https://example.com/api" placeholder="输入服务端地址,如 https://example.com/xiaozhi"
custom-class="!border-none !bg-transparent h-[88rpx] px-[24rpx] items-center" custom-class="!border-none !bg-transparent h-[88rpx] px-[24rpx] items-center"
input-class="text-[28rpx] text-[#232338]" input-class="text-[28rpx] text-[#232338]"
@input="validateUrl"
@blur="validateUrl"
/> />
</view> </view>
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
{{ urlError }}
</text>
</view> </view>
<view class="flex gap-[16rpx]"> <view class="flex gap-[16rpx]">
@@ -320,7 +370,8 @@ onMounted(async () => {
</view> </view>
<!-- 底部安全距离 --> <!-- 底部安全距离 -->
<view style="height: env(safe-area-inset-bottom);" /> <!-- 底部安全距离 -->
<view style="height: env(safe-area-inset-bottom);" />
</view> </view>
</view> </view>
</template> </template>