The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.
The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.
Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
- dead .n-* class names in style blocks
- dead --n-* custom properties
- naive-ui in package.json
Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
- a-input-password defaults to masked; passing default-visibility="false"
would have exposed API keys in clear text
- a-upload fires a real XHR unless auto-upload is false
- nested Trigger components fight over the first element child, so the
group-chat settings dropdown was flattened
- Arco renders tooltip default-slot text inline, which was silently
collapsing the chat and group-chat toolbars
132 lines
3.1 KiB
Vue
132 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const props = defineProps<{
|
|
platformKey?: string
|
|
name: string
|
|
icon: string
|
|
config: Record<string, any>
|
|
credentials?: Record<string, any>
|
|
exclusive?: boolean
|
|
}>()
|
|
|
|
const expanded = ref(true)
|
|
const { t } = useI18n()
|
|
|
|
const configured = computed(() => {
|
|
const creds = props.credentials
|
|
if (!creds) return false
|
|
if (props.platformKey === 'matrix') {
|
|
const extra = creds.extra || {}
|
|
const homeserver = String(extra.homeserver || '').trim()
|
|
const token = String(creds.token || '').trim()
|
|
const userId = String(extra.user_id || '').trim()
|
|
const password = String(extra.password || '').trim()
|
|
return Boolean(homeserver && (token || (userId && password)))
|
|
}
|
|
const keys = ['token', 'api_key', 'app_id', 'client_id', 'secret', 'app_secret', 'client_secret', 'access_token', 'bot_id', 'account_id', 'enabled']
|
|
// Check top-level and nested extra.*
|
|
const targets = [creds, creds.extra].filter(Boolean)
|
|
return targets.some(obj =>
|
|
keys.some(key => {
|
|
const val = (obj as Record<string, any>)[key]
|
|
return val !== undefined && val !== null && val !== '' && val !== false
|
|
})
|
|
)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="platform-card" :class="{ configured }">
|
|
<div class="platform-card-header" @click="expanded = !expanded">
|
|
<div class="platform-info">
|
|
<span class="platform-icon" v-html="icon" />
|
|
<span class="platform-name">{{ name }}</span>
|
|
<a-tag :color="configured ? 'green' : 'gray'" size="small">
|
|
{{ configured ? t('common.configured') : t('common.notConfigured') }}
|
|
</a-tag>
|
|
</div>
|
|
<span class="expand-icon" :class="{ expanded }">▾</span>
|
|
</div>
|
|
<div v-if="expanded" class="platform-card-body">
|
|
<a-alert v-if="exclusive" type="warning" :show-icon="true" class="exclusive-alert">
|
|
{{ t('platform.exclusiveTokenWarning') }}
|
|
</a-alert>
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@use '@/styles/variables' as *;
|
|
|
|
.platform-card {
|
|
background-color: $bg-card;
|
|
border: 1px solid $border-color;
|
|
border-radius: $radius-md;
|
|
margin-bottom: 12px;
|
|
overflow: hidden;
|
|
|
|
&.configured {
|
|
border-color: rgba(var(--success-rgb), 0.2);
|
|
}
|
|
}
|
|
|
|
.platform-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
|
|
&:hover {
|
|
background-color: rgba(var(--text-primary-rgb), 0.03);
|
|
}
|
|
}
|
|
|
|
.platform-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.platform-icon {
|
|
width: 18px;
|
|
height: 18px;
|
|
color: $text-secondary;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.platform-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: $text-primary;
|
|
}
|
|
|
|
.expand-icon {
|
|
font-size: 12px;
|
|
color: $text-muted;
|
|
transition: transform 0.2s;
|
|
|
|
&.expanded {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
&:not(.expanded) {
|
|
transform: rotate(-90deg);
|
|
}
|
|
}
|
|
|
|
.platform-card-body {
|
|
padding: 0 16px 12px;
|
|
border-top: 1px solid $border-light;
|
|
}
|
|
|
|
.exclusive-alert {
|
|
margin: 12px 0 4px;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|