refactor: change agent cloud data permission to cloud variables only
- Rename permission from '查看云端数据' to '查看云端变量' - Remove cloud constants API and page for agents - Add cloud variable records API for agents (stream type) - Add agent cloud-variables page with full variable details - Add agent cloud-variable records page (read-only) - Update navigation from cloud-data to cloud-variables - Update i18n keys: cloudData -> cloudVariables - Backend returns var_type, default_value, max_records etc. - Records page supports dynamic columns and date filtering
This commit is contained in:
@@ -93,8 +93,8 @@ const navMain = computed(() => {
|
||||
|
||||
if (user.can_view_cloud_data) {
|
||||
groups[0].items.push({
|
||||
title: '云端数据',
|
||||
url: '/agent/cloud-data',
|
||||
title: t('nav.cloudVariables'),
|
||||
url: '/agent/cloud-variables',
|
||||
icon: Cloud,
|
||||
})
|
||||
}
|
||||
@@ -122,7 +122,7 @@ const breadcrumbs = computed(() => {
|
||||
cards: t('nav.cards'),
|
||||
users: t('nav.users'),
|
||||
finance: t('nav.finance'),
|
||||
'cloud-data': '云端数据',
|
||||
'cloud-variables': t('nav.cloudVariables'),
|
||||
profile: t('nav.profile'),
|
||||
create: t('nav.create'),
|
||||
}
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Cloud, Database, Eye, Globe, Loader2, Variable } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
import api from '@/services/api'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface Application {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
interface CloudItem {
|
||||
id: number
|
||||
name: string
|
||||
app_id: number | null
|
||||
app_name: string
|
||||
scope?: string
|
||||
status: string
|
||||
value?: string
|
||||
description?: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
const activeTab = ref('variables')
|
||||
const applications = ref<Application[]>([])
|
||||
const appFilter = ref<string>('')
|
||||
const variables = ref<CloudItem[]>([])
|
||||
const constants = ref<CloudItem[]>([])
|
||||
|
||||
const filteredVariables = computed(() => {
|
||||
if (!appFilter.value) return variables.value
|
||||
return variables.value.filter(v => String(v.app_id) === appFilter.value)
|
||||
})
|
||||
|
||||
const filteredConstants = computed(() => {
|
||||
if (!appFilter.value) return constants.value
|
||||
return constants.value.filter(c => String(c.app_id) === appFilter.value)
|
||||
})
|
||||
|
||||
const stats = computed(() => ({
|
||||
variables: variables.value.length,
|
||||
constants: constants.value.length,
|
||||
applications: applications.value.length,
|
||||
}))
|
||||
|
||||
async function fetchApplications() {
|
||||
try {
|
||||
const data = await api.get<{ apps: Application[] }>('/agent/apps')
|
||||
applications.value = data?.apps || []
|
||||
}
|
||||
catch {
|
||||
applications.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchVariables() {
|
||||
try {
|
||||
const data = await api.get<{ variables: CloudItem[] }>('/agent/cloud-variables')
|
||||
variables.value = data?.variables || []
|
||||
}
|
||||
catch {
|
||||
variables.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchConstants() {
|
||||
try {
|
||||
const data = await api.get<{ constants: CloudItem[] }>('/agent/cloud-constants')
|
||||
constants.value = data?.constants || []
|
||||
}
|
||||
catch {
|
||||
constants.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function getAppName(appId: number | null) {
|
||||
if (!appId) return '-'
|
||||
const app = applications.value.find(a => a.id === appId)
|
||||
return app?.name || String(appId)
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
return dateStr.replace('T', ' ').substring(0, 19)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
await Promise.all([fetchApplications(), fetchVariables(), fetchConstants()])
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicPage
|
||||
:title="t('agent.cloudData.title')"
|
||||
:description="t('agent.cloudData.description')"
|
||||
:breadcrumbs="[
|
||||
{ title: t('nav.dashboard'), href: '/agent' },
|
||||
{ title: t('nav.cloudData') },
|
||||
]"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudData.variables') }}
|
||||
</UiCardTitle>
|
||||
<Variable class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ stats.variables }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudData.constants') }}
|
||||
</UiCardTitle>
|
||||
<Database class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ stats.constants }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudData.applications') }}
|
||||
</UiCardTitle>
|
||||
<Globe class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ stats.applications }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<UiCardTitle class="flex items-center gap-2">
|
||||
<Cloud class="size-5" />
|
||||
{{ t('agent.cloudData.dataList') }}
|
||||
</UiCardTitle>
|
||||
<UiCardDescription>
|
||||
{{ t('agent.cloudData.readOnlyHint') }}
|
||||
</UiCardDescription>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<UiSelect v-model="appFilter">
|
||||
<UiSelectTrigger class="w-[180px]">
|
||||
<UiSelectValue :placeholder="t('agent.cloudData.allApps')" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectItem v-for="app in applications" :key="app.id" :value="String(app.id)">
|
||||
{{ app.name }}
|
||||
</UiSelectItem>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
<div class="flex border rounded-lg overflow-hidden">
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium transition-colors"
|
||||
:class="activeTab === 'variables' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'"
|
||||
@click="activeTab = 'variables'"
|
||||
>
|
||||
{{ t('agent.cloudData.variables') }}
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-medium transition-colors"
|
||||
:class="activeTab === 'constants' ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'"
|
||||
@click="activeTab = 'constants'"
|
||||
>
|
||||
{{ t('agent.cloudData.constants') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<Loader2 class="size-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="activeTab === 'variables'">
|
||||
<div v-if="filteredVariables.length === 0" class="text-center py-12 text-muted-foreground">
|
||||
{{ t('agent.cloudData.noVariables') }}
|
||||
</div>
|
||||
<div v-else class="border rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-muted/50">
|
||||
<tr>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.name') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.application') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.scope') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.status') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.createdAt') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="v in filteredVariables" :key="v.id" class="border-t hover:bg-muted/30 transition-colors">
|
||||
<td class="p-3 font-medium">
|
||||
{{ v.name }}
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground">
|
||||
{{ v.app_name || getAppName(v.app_id) }}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge variant="outline">
|
||||
{{ v.scope || 'global' }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge :variant="v.status === 'active' ? 'default' : 'secondary'">
|
||||
{{ v.status === 'active' ? t('agent.cloudData.statusActive') : t('agent.cloudData.statusInactive') }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground">
|
||||
{{ formatDate(v.created_at) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'constants'">
|
||||
<div v-if="filteredConstants.length === 0" class="text-center py-12 text-muted-foreground">
|
||||
{{ t('agent.cloudData.noConstants') }}
|
||||
</div>
|
||||
<div v-else class="border rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-muted/50">
|
||||
<tr>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.name') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.application') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.value') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.status') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudData.columns.createdAt') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="c in filteredConstants" :key="c.id" class="border-t hover:bg-muted/30 transition-colors">
|
||||
<td class="p-3 font-medium">
|
||||
{{ c.name }}
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground">
|
||||
{{ c.app_name || getAppName(c.app_id) }}
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground max-w-[200px] truncate">
|
||||
{{ c.value }}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge :variant="c.status === 'active' ? 'default' : 'secondary'">
|
||||
{{ c.status === 'active' ? t('agent.cloudData.statusActive') : t('agent.cloudData.statusInactive') }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground">
|
||||
{{ formatDate(c.created_at) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="mt-4 flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Eye class="size-4" />
|
||||
<span>{{ t('agent.cloudData.readOnlyNote') }}</span>
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
</BasicPage>
|
||||
</template>
|
||||
@@ -0,0 +1,386 @@
|
||||
<script setup lang="ts">
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
|
||||
import { List, Search } from 'lucide-vue-next'
|
||||
import { computed, h, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import DataTable from '@/components/data-table/data-table.vue'
|
||||
import { generateVueTable } from '@/components/data-table/use-generate-vue-table'
|
||||
import DataTableViewOptions from '@/components/data-table/view-options.vue'
|
||||
import DateTimePicker from '@/components/ui/date-picker/DateTimePicker.vue'
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
import Badge from '@/components/ui/badge/Badge.vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import api from '@/services/api'
|
||||
|
||||
interface Variable {
|
||||
id: number
|
||||
key: string
|
||||
var_type: string
|
||||
scope: string
|
||||
max_records: number
|
||||
}
|
||||
|
||||
interface VariableRecord {
|
||||
id: number
|
||||
data: any
|
||||
user_id?: number
|
||||
user?: {
|
||||
id: number
|
||||
username: string
|
||||
}
|
||||
created_at: string
|
||||
}
|
||||
|
||||
function parseRecordData(record: VariableRecord): Record<string, any> {
|
||||
const rawData = record.data
|
||||
if (rawData == null || rawData === '') return {}
|
||||
if (typeof rawData === 'object') {
|
||||
if (Array.isArray(rawData)) {
|
||||
if (rawData.length > 0 && typeof rawData[0] === 'object' && rawData[0] !== null && !Array.isArray(rawData[0])) {
|
||||
return rawData[0] as Record<string, any>
|
||||
}
|
||||
const obj: Record<string, any> = {}
|
||||
rawData.forEach((item, index) => { obj[`item_${index}`] = item })
|
||||
return obj
|
||||
}
|
||||
return rawData as Record<string, any>
|
||||
}
|
||||
if (typeof rawData === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(rawData)
|
||||
if (parsed != null && typeof parsed === 'object') {
|
||||
if (Array.isArray(parsed)) {
|
||||
if (parsed.length > 0 && typeof parsed[0] === 'object' && parsed[0] !== null && !Array.isArray(parsed[0])) {
|
||||
return parsed[0] as Record<string, any>
|
||||
}
|
||||
const obj: Record<string, any> = {}
|
||||
parsed.forEach((item: any, index: number) => { obj[`item_${index}`] = item })
|
||||
return obj
|
||||
}
|
||||
return parsed as Record<string, any>
|
||||
}
|
||||
return {}
|
||||
}
|
||||
catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
const variable = ref<Variable | null>(null)
|
||||
const records = ref<VariableRecord[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const dateRange = ref<{ from: string, to: string }>({ from: '', to: '' })
|
||||
const searchQuery = ref('')
|
||||
|
||||
const dynamicKeys = computed(() => {
|
||||
const keys = new Set<string>()
|
||||
records.value.forEach((record) => {
|
||||
const data = parseRecordData(record)
|
||||
if (data && typeof data === 'object') {
|
||||
Object.keys(data).forEach(key => keys.add(key))
|
||||
}
|
||||
})
|
||||
return Array.from(keys).sort()
|
||||
})
|
||||
|
||||
const columns = computed<ColumnDef<VariableRecord>[]>(() => {
|
||||
const cols: ColumnDef<VariableRecord>[] = []
|
||||
|
||||
cols.push({
|
||||
accessorKey: 'id',
|
||||
header: () => 'ID',
|
||||
cell: ({ row }) => h('div', { class: 'max-w-xs truncate' }, String(row.original.id)),
|
||||
size: 80,
|
||||
})
|
||||
|
||||
if (variable.value?.scope === 'user') {
|
||||
cols.push({
|
||||
accessorKey: 'user',
|
||||
header: () => t('agent.cloudVariables.records.user'),
|
||||
cell: ({ row }) => {
|
||||
const user = row.original.user
|
||||
if (user) {
|
||||
return h(Badge, { variant: 'outline' }, () => user.username)
|
||||
}
|
||||
return h('span', { class: 'text-muted-foreground' }, '-')
|
||||
},
|
||||
size: 120,
|
||||
})
|
||||
}
|
||||
|
||||
dynamicKeys.value.forEach((key) => {
|
||||
cols.push({
|
||||
id: `data_${key}`,
|
||||
header: () => key,
|
||||
accessorFn: (row) => {
|
||||
const data = parseRecordData(row)
|
||||
return data?.[key]
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const data = parseRecordData(row.original)
|
||||
const value = data?.[key]
|
||||
if (value === null || value === undefined) {
|
||||
return h('span', { class: 'text-muted-foreground' }, '-')
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return h('div', { class: 'max-w-xs truncate', title: JSON.stringify(value) }, JSON.stringify(value))
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return h(Badge, { variant: value ? 'default' : 'secondary' }, () => String(value))
|
||||
}
|
||||
return h('div', { class: 'max-w-xs truncate', title: String(value) }, String(value))
|
||||
},
|
||||
size: 150,
|
||||
})
|
||||
})
|
||||
|
||||
if (dynamicKeys.value.length === 0) {
|
||||
cols.push({
|
||||
id: 'data',
|
||||
header: () => t('agent.cloudVariables.records.data'),
|
||||
cell: ({ row }) => {
|
||||
const data = parseRecordData(row.original)
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
return h('span', { class: 'text-muted-foreground' }, '-')
|
||||
}
|
||||
return h('div', { class: 'max-w-md truncate', title: JSON.stringify(data) }, JSON.stringify(data))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
cols.push({
|
||||
accessorKey: 'created_at',
|
||||
header: () => t('agent.cloudVariables.records.createdAt'),
|
||||
cell: ({ row }) => {
|
||||
const createdAt = row.original.created_at
|
||||
if (!createdAt) return '-'
|
||||
try {
|
||||
const date = new Date(createdAt)
|
||||
if (Number.isNaN(date.getTime())) return '-'
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
catch {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
size: 160,
|
||||
})
|
||||
|
||||
return cols
|
||||
})
|
||||
|
||||
const serverPagination = computed(() => ({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
onPageChange: (newPage: number) => {
|
||||
page.value = newPage
|
||||
},
|
||||
onPageSizeChange: (newPageSize: number) => {
|
||||
pageSize.value = newPageSize
|
||||
page.value = 1
|
||||
},
|
||||
}))
|
||||
|
||||
const table = generateVueTable<VariableRecord>({
|
||||
get data() { return records.value },
|
||||
get loading() { return loading.value },
|
||||
get columns() { return columns.value },
|
||||
serverPagination: serverPagination.value,
|
||||
})
|
||||
|
||||
const columnLabels = computed(() => {
|
||||
const labels: Record<string, string> = {
|
||||
id: 'ID',
|
||||
created_at: 'agent.cloudVariables.records.createdAt',
|
||||
}
|
||||
if (variable.value?.scope === 'user') {
|
||||
labels.user = 'agent.cloudVariables.records.user'
|
||||
}
|
||||
dynamicKeys.value.forEach((key) => {
|
||||
labels[`data_${key}`] = key
|
||||
})
|
||||
return labels
|
||||
})
|
||||
|
||||
watch([page, pageSize], () => {
|
||||
fetchRecords()
|
||||
})
|
||||
|
||||
watch(dateRange, (newVal, oldVal) => {
|
||||
if (newVal.from !== oldVal.from || newVal.to !== oldVal.to) {
|
||||
page.value = 1
|
||||
fetchRecords()
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
async function fetchVariable() {
|
||||
try {
|
||||
const data = await api.get<{ variables: Variable[] }>(`/agent/cloud-variables`)
|
||||
const vars = data?.variables || []
|
||||
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
|
||||
if (variable.value && variable.value.var_type !== 'stream') {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRecords() {
|
||||
if (!variable.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(page.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
if (dateRange.value.from) {
|
||||
params.append('start_date', dateRange.value.from.split('T')[0])
|
||||
}
|
||||
if (dateRange.value.to) {
|
||||
params.append('end_date', dateRange.value.to.split('T')[0])
|
||||
}
|
||||
|
||||
const data = await api.get<{ records: VariableRecord[], total: number }>(`/agent/cloud-variables/${variable.value.id}/records?${params}`)
|
||||
records.value = data?.records || []
|
||||
total.value = data?.total || 0
|
||||
}
|
||||
catch {
|
||||
records.value = []
|
||||
total.value = 0
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchVariable()
|
||||
if (variable.value) {
|
||||
fetchRecords()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicPage
|
||||
:title="t('agent.cloudVariables.records.title')"
|
||||
:description="t('agent.cloudVariables.records.description', { key: variable?.key || '' })"
|
||||
:breadcrumbs="[
|
||||
{ title: t('nav.dashboard'), href: '/agent' },
|
||||
{ title: t('nav.cloudVariables'), href: '/agent/cloud-variables' },
|
||||
{ title: t('agent.cloudVariables.records.title') },
|
||||
]"
|
||||
sticky
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.records.totalRecords') }}
|
||||
</UiCardTitle>
|
||||
<List class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ total }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.types.stream') }}
|
||||
</UiCardTitle>
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ variable?.key || '-' }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.records.maxRecordsLabel') }}
|
||||
</UiCardTitle>
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ variable?.max_records || t('agent.cloudVariables.records.unlimited') }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.columns.scope') }}
|
||||
</UiCardTitle>
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ variable?.scope === 'user' ? t('agent.cloudVariables.userScope') : t('agent.cloudVariables.appScope') }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
|
||||
<UiCard>
|
||||
<UiCardContent class="p-6">
|
||||
<DataTable :columns="columns" :data="records" :loading="loading" :table="table" :server-pagination="serverPagination" @refresh="fetchRecords">
|
||||
<template #toolbar>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
v-model="searchQuery"
|
||||
:placeholder="t('agent.cloudVariables.records.searchPlaceholder')"
|
||||
class="pl-8 h-8 w-[150px] lg:w-[250px]"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<DateTimePicker
|
||||
v-model="dateRange.from"
|
||||
:placeholder="t('agent.cloudVariables.records.startDate')"
|
||||
class="w-[180px]"
|
||||
/>
|
||||
<span class="text-muted-foreground text-sm">{{ t('agent.finance.to') }}</span>
|
||||
<DateTimePicker
|
||||
v-model="dateRange.to"
|
||||
:placeholder="t('agent.cloudVariables.records.endDate')"
|
||||
class="w-[180px]"
|
||||
/>
|
||||
</div>
|
||||
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
</BasicPage>
|
||||
</template>
|
||||
@@ -0,0 +1,305 @@
|
||||
<script setup lang="ts">
|
||||
import { Globe, LayoutGrid, List, User, Variable } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import SingleFilter from '@/components/data-table/single-filter.vue'
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
import api from '@/services/api'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
interface Application {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
interface CloudVariable {
|
||||
id: number
|
||||
key: string
|
||||
app_id: number | null
|
||||
application_name: string
|
||||
default_value: string
|
||||
var_type: string
|
||||
max_records: number
|
||||
scope: string
|
||||
write_permission: string
|
||||
status: string
|
||||
description: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
const variables = ref<CloudVariable[]>([])
|
||||
const applications = ref<Application[]>([])
|
||||
|
||||
const appFilter = ref<string>('')
|
||||
const scopeFilter = ref<string>('')
|
||||
|
||||
const filteredVariables = computed(() => {
|
||||
let result = [...variables.value]
|
||||
if (appFilter.value) {
|
||||
result = result.filter(v => String(v.app_id) === appFilter.value)
|
||||
}
|
||||
if (scopeFilter.value) {
|
||||
result = result.filter(v => v.scope === scopeFilter.value)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const appScopeCount = computed(() => variables.value.filter(v => v.scope === 'app').length)
|
||||
const userScopeCount = computed(() => variables.value.filter(v => v.scope === 'user').length)
|
||||
|
||||
const applicationOptions = computed(() =>
|
||||
applications.value.map(app => ({ label: app.name, value: String(app.id) })),
|
||||
)
|
||||
|
||||
const scopeOptions = computed(() => [
|
||||
{ label: t('agent.cloudVariables.appScope'), value: 'app' },
|
||||
{ label: t('agent.cloudVariables.userScope'), value: 'user' },
|
||||
])
|
||||
|
||||
async function fetchApplications() {
|
||||
try {
|
||||
const data = await api.get<{ apps: Application[] }>('/agent/apps')
|
||||
applications.value = data?.apps || []
|
||||
}
|
||||
catch {
|
||||
applications.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchVariables() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ variables: CloudVariable[] }>('/agent/cloud-variables')
|
||||
variables.value = data?.variables || []
|
||||
}
|
||||
catch {
|
||||
variables.value = []
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleViewRecords(variable: CloudVariable) {
|
||||
router.push(`/agent/cloud-variables/${variable.id}/records?app_id=${variable.app_id}`)
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
if (!dateStr) return '-'
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
if (Number.isNaN(date.getTime())) return '-'
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
catch {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
function formatVarType(type: string) {
|
||||
const map: Record<string, string> = {
|
||||
integer: t('agent.cloudVariables.types.integer'),
|
||||
decimal: t('agent.cloudVariables.types.decimal'),
|
||||
string: t('agent.cloudVariables.types.string'),
|
||||
binary: t('agent.cloudVariables.types.binary'),
|
||||
stream: t('agent.cloudVariables.types.stream'),
|
||||
}
|
||||
return map[type || 'string'] || type || 'string'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchApplications()
|
||||
fetchVariables()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicPage
|
||||
:title="t('agent.cloudVariables.title')"
|
||||
:description="t('agent.cloudVariables.description')"
|
||||
:breadcrumbs="[
|
||||
{ title: t('nav.dashboard'), href: '/agent' },
|
||||
{ title: t('nav.cloudVariables') },
|
||||
]"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.totalVariables') }}
|
||||
</UiCardTitle>
|
||||
<Variable class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ filteredVariables.length }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.appScopeVariables') }}
|
||||
</UiCardTitle>
|
||||
<Globe class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ appScopeCount }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.userScopeVariables') }}
|
||||
</UiCardTitle>
|
||||
<User class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ userScopeCount }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
<UiCard>
|
||||
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
{{ t('agent.cloudVariables.appCount') }}
|
||||
</UiCardTitle>
|
||||
<LayoutGrid class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ applications.length }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
|
||||
<UiCard>
|
||||
<UiCardContent class="p-6">
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2">
|
||||
<SingleFilter
|
||||
v-model="appFilter"
|
||||
:title="t('agent.cloudVariables.application')"
|
||||
:options="applicationOptions"
|
||||
/>
|
||||
<SingleFilter
|
||||
v-model="scopeFilter"
|
||||
:title="t('agent.cloudVariables.scope')"
|
||||
:options="scopeOptions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<div class="size-8 animate-spin rounded-full border-4 border-muted border-t-primary" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="filteredVariables.length === 0" class="text-center py-12 text-muted-foreground">
|
||||
{{ t('agent.cloudVariables.noVariables') }}
|
||||
</div>
|
||||
|
||||
<div v-else class="border rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-muted/50">
|
||||
<tr>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.key') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.defaultValue') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.type') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.scope') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.application') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.status') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('agent.cloudVariables.columns.createdAt') }}
|
||||
</th>
|
||||
<th class="text-left p-3 font-medium">
|
||||
{{ t('common.actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="v in filteredVariables"
|
||||
:key="v.id"
|
||||
class="border-t hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
<td class="p-3">
|
||||
<code class="text-xs bg-muted px-2 py-1 rounded font-mono">{{ v.key }}</code>
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground max-w-[200px] truncate">
|
||||
{{ v.default_value || '-' }}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge variant="outline">
|
||||
{{ formatVarType(v.var_type) }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge :variant="v.scope === 'app' ? 'default' : 'secondary'">
|
||||
{{ v.scope === 'app' ? t('agent.cloudVariables.appScope') : t('agent.cloudVariables.userScope') }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge v-if="v.application_name" variant="outline">
|
||||
{{ v.application_name }}
|
||||
</UiBadge>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiBadge :variant="v.status === 'active' ? 'default' : 'destructive'">
|
||||
{{ v.status === 'active' ? t('agent.cloudVariables.statusActive') : t('agent.cloudVariables.statusInactive') }}
|
||||
</UiBadge>
|
||||
</td>
|
||||
<td class="p-3 text-muted-foreground whitespace-nowrap">
|
||||
{{ formatDate(v.created_at) }}
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<UiButton
|
||||
v-if="v.var_type === 'stream'"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="handleViewRecords(v)"
|
||||
>
|
||||
<List class="mr-1 size-4" />
|
||||
{{ t('agent.cloudVariables.viewRecords') }}
|
||||
</UiButton>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
</BasicPage>
|
||||
</template>
|
||||
@@ -923,7 +923,7 @@
|
||||
"maxApiCalls": "API Calls/Month",
|
||||
"maxApiCallsPlaceholder": "-1 for unlimited",
|
||||
"allowAgent": "Agent Permission",
|
||||
"allowCloudData": "Cloud Data",
|
||||
"allowCloudData": "Cloud Variables",
|
||||
"allowDynamicCode": "Cloud Function",
|
||||
"allowEmail": "Email Feature",
|
||||
"allowExtension": "Extension API",
|
||||
@@ -1286,7 +1286,7 @@
|
||||
"cardsCount": "Cards Generated",
|
||||
"childAgentsCount": "Child Agents",
|
||||
"canCreateAgent": "Create Agent Permission",
|
||||
"canViewCloudData": "Cloud Data Permission",
|
||||
"canViewCloudData": "View Cloud Variables Permission",
|
||||
"balance": "Balance",
|
||||
"createdAt": "Created At",
|
||||
"lastLoginAt": "Last Login"
|
||||
@@ -2887,27 +2887,49 @@
|
||||
"generateNow": "Generate Now"
|
||||
}
|
||||
},
|
||||
"cloudData": {
|
||||
"title": "Cloud Data",
|
||||
"description": "View cloud variables and constants (read-only)",
|
||||
"variables": "Cloud Variables",
|
||||
"constants": "Cloud Constants",
|
||||
"applications": "Applications",
|
||||
"dataList": "Data List",
|
||||
"readOnlyHint": "Read-only mode, data cannot be edited",
|
||||
"allApps": "All Applications",
|
||||
"cloudVariables": {
|
||||
"title": "Cloud Variables",
|
||||
"description": "View cloud variables (read-only)",
|
||||
"totalVariables": "Total Variables",
|
||||
"appScopeVariables": "App Scope Variables",
|
||||
"userScopeVariables": "User Scope Variables",
|
||||
"appCount": "Applications",
|
||||
"noVariables": "No cloud variables found",
|
||||
"noConstants": "No cloud constants found",
|
||||
"appScope": "App",
|
||||
"userScope": "User",
|
||||
"statusActive": "Active",
|
||||
"statusInactive": "Inactive",
|
||||
"readOnlyNote": "Read-only - Data is for viewing only, no editing allowed",
|
||||
"viewRecords": "View Records",
|
||||
"application": "Application",
|
||||
"scope": "Scope",
|
||||
"types": {
|
||||
"integer": "Integer",
|
||||
"decimal": "Decimal",
|
||||
"string": "String",
|
||||
"binary": "Binary",
|
||||
"stream": "Stream"
|
||||
},
|
||||
"columns": {
|
||||
"name": "Name",
|
||||
"application": "Application",
|
||||
"key": "Key",
|
||||
"defaultValue": "Default Value",
|
||||
"type": "Type",
|
||||
"scope": "Scope",
|
||||
"value": "Value",
|
||||
"application": "Application",
|
||||
"status": "Status",
|
||||
"createdAt": "Created At"
|
||||
},
|
||||
"records": {
|
||||
"title": "Variable Records",
|
||||
"description": "Records for variable {key}",
|
||||
"totalRecords": "Total Records",
|
||||
"maxRecordsLabel": "Max Records",
|
||||
"unlimited": "Unlimited",
|
||||
"user": "User",
|
||||
"data": "Data",
|
||||
"createdAt": "Created At",
|
||||
"searchPlaceholder": "Search records...",
|
||||
"startDate": "Start Date",
|
||||
"endDate": "End Date"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2932,7 +2954,7 @@
|
||||
"apiCalls": "{count} API calls/month",
|
||||
"unlimitedApiCalls": "Unlimited API calls",
|
||||
"agent": "Agent permission",
|
||||
"cloudData": "Cloud data feature",
|
||||
"cloudData": "Cloud Variables feature",
|
||||
"dynamicCode": "Dynamic code feature",
|
||||
"email": "Email feature",
|
||||
"extension": "Extension API",
|
||||
|
||||
@@ -924,7 +924,7 @@
|
||||
"maxApiCalls": "API调用次数/月",
|
||||
"maxApiCallsPlaceholder": "-1表示不限",
|
||||
"allowAgent": "代理权限",
|
||||
"allowCloudData": "云端数据",
|
||||
"allowCloudData": "云端变量",
|
||||
"allowDynamicCode": "云端函数",
|
||||
"allowEmail": "邮件功能",
|
||||
"allowExtension": "扩展接口",
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"cardsCount": "生成卡密",
|
||||
"childAgentsCount": "下级代理",
|
||||
"canCreateAgent": "创建代理权限",
|
||||
"canViewCloudData": "云端数据权限",
|
||||
"canViewCloudData": "查看云端变量权限",
|
||||
"balance": "余额",
|
||||
"totalConsumption": "消费",
|
||||
"createdAt": "注册时间",
|
||||
@@ -2888,27 +2888,49 @@
|
||||
"generateNow": "立即生成"
|
||||
}
|
||||
},
|
||||
"cloudData": {
|
||||
"title": "云端数据",
|
||||
"description": "查看云端变量和常量数据(只读)",
|
||||
"variables": "云端变量",
|
||||
"constants": "云端常量",
|
||||
"applications": "应用数量",
|
||||
"dataList": "数据列表",
|
||||
"readOnlyHint": "只读模式,数据不可编辑",
|
||||
"allApps": "全部应用",
|
||||
"cloudVariables": {
|
||||
"title": "云端变量",
|
||||
"description": "查看云端变量数据(只读)",
|
||||
"totalVariables": "变量总数",
|
||||
"appScopeVariables": "应用级变量",
|
||||
"userScopeVariables": "用户级变量",
|
||||
"appCount": "应用数量",
|
||||
"noVariables": "暂无云端变量数据",
|
||||
"noConstants": "暂无云端常量数据",
|
||||
"appScope": "应用级",
|
||||
"userScope": "用户级",
|
||||
"statusActive": "启用",
|
||||
"statusInactive": "禁用",
|
||||
"readOnlyNote": "只读模式 - 当前数据仅供查看,无法进行编辑操作",
|
||||
"viewRecords": "查看记录",
|
||||
"application": "应用",
|
||||
"scope": "作用域",
|
||||
"types": {
|
||||
"integer": "整数",
|
||||
"decimal": "小数",
|
||||
"string": "字符串",
|
||||
"binary": "二进制",
|
||||
"stream": "列表"
|
||||
},
|
||||
"columns": {
|
||||
"name": "名称",
|
||||
"application": "应用",
|
||||
"key": "变量名",
|
||||
"defaultValue": "默认值",
|
||||
"type": "类型",
|
||||
"scope": "作用域",
|
||||
"value": "值",
|
||||
"application": "应用",
|
||||
"status": "状态",
|
||||
"createdAt": "创建时间"
|
||||
},
|
||||
"records": {
|
||||
"title": "变量记录",
|
||||
"description": "变量 {key} 的记录列表",
|
||||
"totalRecords": "记录总数",
|
||||
"maxRecordsLabel": "最大记录数",
|
||||
"unlimited": "无限制",
|
||||
"user": "用户",
|
||||
"data": "数据",
|
||||
"createdAt": "创建时间",
|
||||
"searchPlaceholder": "搜索记录...",
|
||||
"startDate": "开始日期",
|
||||
"endDate": "结束日期"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2933,7 +2955,7 @@
|
||||
"apiCalls": "{count}次/月 API调用",
|
||||
"unlimitedApiCalls": "无限API调用",
|
||||
"agent": "代理权限",
|
||||
"cloudData": "云端数据功能",
|
||||
"cloudData": "云端变量功能",
|
||||
"dynamicCode": "云端函数功能",
|
||||
"email": "邮件功能",
|
||||
"extension": "扩展接口功能",
|
||||
|
||||
@@ -451,10 +451,16 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: { title: '财务管理 - 代理商后台' },
|
||||
},
|
||||
{
|
||||
path: 'cloud-data',
|
||||
name: 'AgentCloudData',
|
||||
component: () => import('@/pages/agent/cloud-data/index.vue'),
|
||||
meta: { title: '云端数据 - 代理商后台' },
|
||||
path: 'cloud-variables',
|
||||
name: 'AgentCloudVariables',
|
||||
component: () => import('@/pages/agent/cloud-variables/index.vue'),
|
||||
meta: { title: '云端变量 - 代理商后台' },
|
||||
},
|
||||
{
|
||||
path: 'cloud-variables/:id/records',
|
||||
name: 'AgentCloudVariableRecords',
|
||||
component: () => import('@/pages/agent/cloud-variables/[id]/records.vue'),
|
||||
meta: { title: '变量记录 - 代理商后台' },
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
|
||||
Reference in New Issue
Block a user