第一次打包失败

This commit is contained in:
2026-07-02 18:01:56 +08:00
parent 3544208baa
commit 879d2e350a
75 changed files with 7499 additions and 2267 deletions

View File

@@ -0,0 +1,546 @@
/* ========== 通用 ========== */
.status-bar {
height: var(--status-bar-height);
background-color: #ffffff;
}
.page-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f6fa;
}
/* ========== 导航栏 ========== */
.cloud-db-header {
background-color: #ffffff;
flex-shrink: 0;
position: relative;
z-index: 10;
}
.custom-navbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
}
.navbar-left {
display: flex;
align-items: center;
min-width: 120rpx;
}
.custom-navbar-icon {
font-size: 40rpx;
color: #333;
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.navbar-right {
min-width: 120rpx;
display: flex;
justify-content: flex-end;
}
/* 下拉菜单 */
.menu-card {
position: absolute;
top: 88rpx;
right: 20rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
z-index: 500;
overflow: hidden;
min-width: 200rpx;
}
.menu-card-item {
padding: 24rpx 36rpx;
font-size: 28rpx;
color: #333;
text-align: center;
}
.menu-card-item:active {
background-color: #f5f6fa;
}
.solid-line {
height: 1rpx;
background-color: #eee;
margin: 0 20rpx;
}
/* ========== 数据库信息栏 ========== */
.db-info-bar {
display: flex;
align-items: center;
gap: 24rpx;
padding: 20rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.db-info-text {
font-size: 24rpx;
color: #999;
}
/* ========== 内容区域 ========== */
.cloud-db-content {
flex: 1;
overflow-y: auto;
}
.section-block {
margin: 20rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f5f6fa;
}
.section-title {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.section-title .iconfont {
font-size: 36rpx;
color: #3b86ff;
}
.section-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
.row-count {
font-size: 24rpx;
color: #999;
}
.section-divider {
height: 1rpx;
background-color: #f0f0f0;
margin: 0 20rpx;
}
.section-body {
padding: 16rpx 0;
}
/* ========== 空状态 ========== */
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
color: #ccc;
font-size: 26rpx;
}
.empty-tip .iconfont {
font-size: 80rpx;
margin-bottom: 16rpx;
}
/* ========== 表列表 ========== */
.table-list {
padding: 0 30rpx;
}
.table-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f6fa;
}
.table-item:last-child {
border-bottom: none;
}
.table-item:active {
background-color: #fafbfc;
margin: 0 -30rpx;
padding-left: 30rpx;
padding-right: 30rpx;
}
.table-icon {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
background: rgba(59, 134, 255, 0.08);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
}
.table-info {
flex: 1;
overflow: hidden;
}
.table-name {
font-size: 30rpx;
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-desc {
font-size: 24rpx;
color: #999;
margin-top: 4rpx;
}
.table-meta {
display: flex;
align-items: center;
gap: 8rpx;
flex-shrink: 0;
}
/* ========== 数据表格 ========== */
.data-table-wrapper {
padding: 0;
}
.data-table-scroll {
width: 100%;
}
.data-table {
min-width: 100%;
}
.data-row {
display: flex;
flex-direction: row;
border-bottom: 1rpx solid #f0f0f0;
}
.data-row:last-child {
border-bottom: none;
}
.data-header {
background-color: #f8f9fb;
}
.data-cell {
padding: 16rpx 20rpx;
font-size: 26rpx;
color: #333;
min-width: 160rpx;
max-width: 300rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-right: 1rpx solid #f0f0f0;
flex-shrink: 0;
}
.data-cell:last-child {
border-right: none;
}
.data-header .data-cell {
font-weight: 600;
color: #666;
font-size: 24rpx;
}
.index-cell {
min-width: 80rpx !important;
max-width: 80rpx !important;
text-align: center;
color: #999;
font-size: 24rpx;
}
.data-row:active {
background-color: #f5f7fa;
}
/* ========== 弹窗通用 ========== */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 900;
}
.popup-card {
width: 640rpx;
background: #fff;
border-radius: 24rpx;
padding: 40rpx 36rpx 30rpx;
position: relative;
}
.close-popup-card {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.close-popup-card .iconfont {
font-size: 36rpx;
color: #999;
}
.popup-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.new-folder-name {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
}
.create-folder-name {
margin-bottom: 24rpx;
}
.create-folder-name input {
width: 100%;
height: 80rpx;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
background: #f8f9fb;
}
.upload-file-area {
width: 100%;
height: 120rpx;
border: 2rpx dashed #d0d0d0;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
background: #fafbfc;
}
.selected-file {
font-size: 26rpx;
color: #3b86ff;
}
.upload-hint {
font-size: 24rpx;
color: #999;
}
.option-wrapper {
display: flex;
gap: 20rpx;
margin-top: 10rpx;
}
.opt-btn {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
}
.opt-cancel {
background: #f5f6fa;
color: #666;
}
.opt-confirm {
background: #3b86ff;
color: #fff;
}
/* ========== 权限管理 ========== */
.permission-body {
max-height: 600rpx;
margin-bottom: 20rpx;
}
.permission-list {
padding: 0 10rpx;
}
.permission-item {
display: flex;
align-items: center;
padding: 20rpx 10rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.perm-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.perm-name {
font-size: 28rpx;
color: #333;
}
.perm-type {
font-size: 22rpx;
color: #999;
}
.perm-level {
font-size: 24rpx;
color: #3b86ff;
background: rgba(59, 134, 255, 0.08);
padding: 6rpx 16rpx;
border-radius: 8rpx;
margin: 0 16rpx;
}
.perm-actions {
display: flex;
gap: 12rpx;
}
.perm-action-btn {
font-size: 24rpx;
color: #3b86ff;
padding: 8rpx 16rpx;
border: 1rpx solid #3b86ff;
border-radius: 8rpx;
}
.perm-action-btn.delete {
color: #f56c6c;
border-color: #f56c6c;
}
.close-permission-btn {
text-align: center;
font-size: 28rpx;
color: #999;
padding: 16rpx 0;
}
.folder-null {
text-align: center;
padding: 40rpx 0;
font-size: 26rpx;
color: #999;
}
/* ========== 移交团队 ========== */
.transfer-body {
max-height: 500rpx;
margin-bottom: 20rpx;
}
.transfer-list {
padding: 0 10rpx;
}
.transfer-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 16rpx;
border-bottom: 1rpx solid #f0f0f0;
border-radius: 12rpx;
margin-bottom: 8rpx;
}
.transfer-item.active {
background: rgba(59, 134, 255, 0.06);
}
.transfer-name {
font-size: 28rpx;
color: #333;
}
/* ===== 加载动画 ===== */
.loading-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 0;
gap: 16rpx;
}
.loading-spinner {
width: 56rpx;
height: 56rpx;
border: 4rpx solid #e8e8e8;
border-top-color: #3b86ff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 24rpx;
color: #999;
}

View File

@@ -0,0 +1,715 @@
<template>
<view class="cloud-db-detail-wrapper">
<view class="status-bar"></view>
<!-- ===== 新建表弹窗 ===== -->
<view v-if="showNewTable" class="popup-overlay" @click="closeNewTableModal">
<view class="popup-card" @click.stop>
<view class="close-popup-card" @click="closeNewTableModal">
<view class="iconfont icon-quxiao"></view>
</view>
<view class="popup-title">导入文件到数据库</view>
<view class="new-folder-name">表名称</view>
<view class="create-folder-name">
<input v-model="importTableName" type="text" placeholder="输入表名称" />
</view>
<view class="new-folder-name">文件</view>
<view class="upload-file-area" @click="handlePickFile">
<view v-if="importFileName" class="selected-file">{{ importFileName }}</view>
<view v-else class="upload-hint">点击选择文件支持 CSV/Excel/SQLite</view>
</view>
<view class="option-wrapper">
<view class="opt-btn opt-cancel" @click="closeNewTableModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmImportFile">确认导入</view>
</view>
</view>
</view>
<!-- ===== 权限管理弹窗 ===== -->
<view v-if="showPermission" class="popup-overlay" @click="closePermissionModal">
<view class="popup-card" @click.stop>
<view class="close-popup-card" @click="closePermissionModal">
<view class="iconfont icon-quxiao"></view>
</view>
<view class="popup-title">权限管理</view>
<scroll-view class="permission-body" scroll-y>
<view v-if="permissionList.length === 0" class="folder-null">暂无权限记录</view>
<view v-else class="permission-list">
<view class="permission-item" v-for="perm in permissionList" :key="perm._id || perm.id">
<view class="perm-info">
<!-- <text class="perm-name">{{ perm.name }}</text> -->
<text class="perm-type">{{ perm.permType === 'team_id' ? '团队' : '个人' }}</text>
</view>
<view class="perm-level">{{ getAccessLevelLabel(perm.access_level) }}</view>
<view class="perm-actions">
<view class="perm-action-btn" @click="changePermission(perm, 'change')">修改</view>
<view class="perm-action-btn delete" @click="changePermission(perm, 'delete')">移除</view>
</view>
</view>
</view>
</scroll-view>
<view class="close-permission-btn" @click="closePermissionModal">关闭</view>
</view>
</view>
<!-- ===== 移交团队弹窗 ===== -->
<view v-if="showTransfer" class="popup-overlay" @click="closeTransferModal">
<view class="popup-card" @click.stop>
<view class="close-popup-card" @click="closeTransferModal">
<view class="iconfont icon-quxiao"></view>
</view>
<view class="popup-title">移交数据库到团队</view>
<scroll-view class="transfer-body" scroll-y>
<view v-if="teamList.length === 0" class="folder-null">暂无可用团队</view>
<view v-else class="transfer-list">
<view class="transfer-item" v-for="team in teamList" :key="team.id"
:class="{ active: selectedTeamId === team.id }" @click="selectedTeamId = team.id">
<text class="transfer-name">{{ team.name }}</text>
<uni-icons v-if="selectedTeamId === team.id" type="checkmarkempty" size="20"
color="#3b86ff"></uni-icons>
</view>
</view>
</scroll-view>
<view class="option-wrapper">
<view class="opt-btn opt-cancel" @click="closeTransferModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmTransfer">确认移交</view>
</view>
</view>
</view>
<!-- ===== 主页面 ===== -->
<view class="cloud-db-detail page-container">
<view class="cloud-db-header">
<view class="custom-navbar">
<view class="navbar-left" @click="handleBack">
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
</view>
<view class="navbar-title">{{ databaseName }}</view>
<view class="navbar-right">
<view class="iconfont icon-gengduo custom-navbar-icon" @click="handleMenu"></view>
</view>
</view>
<!-- 下拉菜单 -->
<view v-if="isMenuOpen" class="menu-card">
<view class="menu-card-item" @click="openImportModal(); handleMenu()">导入文件</view>
<view class="solid-line"></view>
<view class="menu-card-item" @click="openPermissionModal(); handleMenu()">权限管理</view>
<view v-if="!teamId" class="solid-line"></view>
<view v-if="!teamId" class="menu-card-item" @click="openTransferModal(); handleMenu()">移交团队</view>
</view>
</view>
<scroll-view class="cloud-db-content" scroll-y>
<!-- 数据库基本信息 -->
<view class="db-info-bar">
<text class="db-info-text">{{ dbTableCount }} 张表</text>
<text class="db-info-text">权限{{ getAccessLevelLabel(dbAccessLevel) }}</text>
</view>
<!-- 表列表 -->
<view class="section-block">
<view class="section-header">
<view class="section-title">
<view class="iconfont icon-biaoge"></view>
<text>数据表</text>
</view>
</view>
<view class="section-body">
<!-- 加载中 -->
<view v-if="isTableLoading" class="loading-area">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="tableList.length === 0" class="empty-tip">
<view class="iconfont icon-shujuku"></view>
<text>暂无数据表</text>
</view>
<view v-else class="table-list">
<view class="table-item" v-for="table in tableList" :key="table.table_name"
@click="selectTable(table)" @longpress="handleDeleteTable(table)">
<view class="table-icon">
<uni-icons type="compose" size="22" color="#3b86ff"></uni-icons>
</view>
<view class="table-info">
<view class="table-name">{{ table.table_name }}</view>
<view class="table-desc">{{ table.row_count || 0 }} 条记录</view>
</view>
<view class="table-meta">
<uni-icons type="right" size="14" color="#999"></uni-icons>
</view>
</view>
</view>
</view>
</view>
<!-- 表数据查看 -->
<view v-if="currentTable" class="section-block">
<view class="section-divider"></view>
<view class="section-header">
<view class="section-title">
<view class="iconfont icon-biaoge"></view>
<text>{{ currentTable.table_name }}</text>
</view>
<view class="section-actions">
<text class="row-count">{{ tableData.length }} / {{ currentTable.row_count || 0 }} </text>
</view>
</view>
<view class="section-body">
<view v-if="tableData.length === 0" class="empty-tip">
<text>暂无数据</text>
</view>
<view v-else class="data-table-wrapper">
<scroll-view scroll-x class="data-table-scroll">
<view class="data-table">
<!-- 表头 -->
<view class="data-row data-header">
<view class="data-cell index-cell">#</view>
<view v-for="col in tableColumns" :key="col" class="data-cell">
{{ col }}
</view>
</view>
<!-- 数据行 -->
<view class="data-row" v-for="(row, rowIdx) in tableData"
:key="row._row_id || rowIdx" @click="openEditRow(row)">
<view class="data-cell index-cell">{{ rowIdx + 1 }}</view>
<view v-for="col in tableColumns" :key="col" class="data-cell">
{{ formatCellValue(row[col]) }}
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import {
onMounted,
ref,
computed
} from 'vue';
import {
getToken,
getUserId
} from '@/utils/user-info.js';
import {
chooseFile
} from '@/uni_modules/lime-choose-file';
import {
getCloudDbTables,
deleteCloudDbTable,
getCloudDbTableData,
updateCloudDbTableData,
importFileToCloudDb,
getCloudDbPermission,
deleteCloudDbPermission,
updateCloudDbPermission,
dbTransferToTeam,
getTeamList
} from '@/utils/cloud-api.js';
// 声明 props 以避免 "Extraneous non-props attributes" 警告
const props = defineProps({
databaseId: {
type: String,
default: ''
},
databaseName: {
type: String,
default: ''
},
teamId: {
type: String,
default: ''
}
});
const databaseId = ref(props.databaseId || '');
const databaseName = ref(props.databaseName || '');
const teamId = ref(props.teamId || '');
const dbAccessLevel = ref(2);
const dbTableCount = ref(0);
const tableList = ref([]);
const isTableLoading = ref(false);
const currentTable = ref(null);
const tableData = ref([]);
const tableColumns = computed(() => {
// 如果有表数据,从数据中提取列名
if (tableData.value.length > 0) {
const keys = new Set();
tableData.value.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
return Array.from(keys).filter(k => k !== '_row_id' && k !== '_id');
}
// 否则使用表结构中的 columns
if (currentTable.value?.columns) {
return currentTable.value.columns.map(c => c.name).filter(n => n !== '_row_id');
}
return [];
});
// 菜单
const isMenuOpen = ref(false);
const handleMenu = () => {
isMenuOpen.value = !isMenuOpen.value;
};
const handleBack = () => {
uni.navigateBack();
};
// ========== 数据加载 ==========
const fetchTables = async () => {
isTableLoading.value = true;
try {
const token = getToken();
const tables = await getCloudDbTables(token, databaseId.value, true);
// API 返回 { tables: [...] } 格式,字段名为 name / columns / row_count
// const tables = result?.tables || result || [];
tableList.value = (tables || []).map(t => ({
table_name: t.name,
row_count: t.row_count || 0,
columns: t.columns || []
}));
dbTableCount.value = tableList.value.length;
} catch (error) {
console.error('获取表列表失败:', error);
tableList.value = [];
} finally {
isTableLoading.value = false;
}
};
const selectTable = async (table) => {
currentTable.value = table;
tableData.value = [];
uni.showLoading({
title: '加载中...'
});
try {
const token = getToken();
const data = await getCloudDbTableData(token, databaseId.value, table.table_name);
tableData.value = data || [];
uni.hideLoading();
} catch (err) {
uni.hideLoading();
uni.showToast({
title: typeof err === 'string' ? err : '加载表数据失败',
icon: 'none'
});
tableData.value = [];
}
};
const handleDeleteTable = (table) => {
uni.showModal({
title: '删除表',
content: `确定要删除表"${table.table_name}"吗?此操作不可恢复。`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: '删除中...'
});
try {
const token = getToken();
await deleteCloudDbTable(token, databaseId.value, table.table_name);
uni.hideLoading();
uni.showToast({
title: '删除成功',
icon: 'success'
});
if (currentTable.value?.table_name === table.table_name) {
currentTable.value = null;
tableData.value = [];
}
await fetchTables();
} catch (error) {
uni.hideLoading();
console.error('删除表失败:', error);
uni.showToast({
title: '删除失败,请重试',
icon: 'error'
});
}
}
}
});
};
// ========== 数据编辑 ==========
const openEditRow = (row) => {
const rowId = row._row_id || row._id;
if (!rowId) {
uni.showToast({
title: '无法识别该行',
icon: 'none'
});
return;
}
// 收集可编辑字段
const columns = tableColumns.value;
if (columns.length === 0) {
uni.showToast({
title: '无可用列',
icon: 'none'
});
return;
}
// 简化:弹出编辑第一个字段
const firstCol = columns[0];
uni.showModal({
title: `编辑 ${firstCol}`,
editable: true,
placeholderText: row[firstCol] || '',
content: '',
success: async (res) => {
if (res.confirm && res.content !== undefined) {
uni.showLoading({
title: '更新中...'
});
try {
const token = getToken();
await updateCloudDbTableData(
token,
databaseId.value,
currentTable.value.table_name, {
_row_id: rowId
}, {
[firstCol]: res.content
}
);
uni.hideLoading();
uni.showToast({
title: '更新成功',
icon: 'success'
});
// 更新本地数据
row[firstCol] = res.content;
} catch (error) {
uni.hideLoading();
console.error('更新失败:', error);
uni.showToast({
title: error,
icon: 'error'
});
}
}
}
});
};
// ========== 导入文件 ==========
const showNewTable = ref(false);
const importTableName = ref('');
const importFileName = ref('');
const importFilePath = ref('');
const openImportModal = () => {
showNewTable.value = true;
importTableName.value = '';
importFileName.value = '';
importFilePath.value = '';
};
const closeNewTableModal = () => {
showNewTable.value = false;
};
const handlePickFile = () => {
chooseFile({
count: 1,
type: 'all',
success: (res) => {
const file = res.tempFiles[0];
importFileName.value = file.name || 'unknown';
importFilePath.value = file.path;
},
fail: (err) => {
console.error('选择文件失败:', err);
uni.showToast({
title: '选择文件失败',
icon: 'none'
});
}
});
};
const confirmImportFile = async () => {
const tableName = importTableName.value.trim() || "";
// if (!tableName) {
// uni.showToast({
// title: '请输入表名称',
// icon: 'none'
// });
// return;
// }
if (!importFilePath.value) {
uni.showToast({
title: '请选择文件',
icon: 'none'
});
return
}
uni.showLoading({
title: '导入中...'
});
try {
const token = getToken();
await importFileToCloudDb(token, false, databaseId.value, importFilePath.value, tableName);
uni.hideLoading();
uni.showToast({
title: '导入成功',
icon: 'success'
});
closeNewTableModal();
await fetchTables();
} catch (error) {
uni.hideLoading();
console.error('导入失败:', error);
if (error === "table_exists"){
uni.showToast({
title: '文件已存在',
icon: 'error'
});
}
else{
uni.showToast({
title: '导入失败,请重试',
icon: 'error'
});
}
}
};
// ========== 权限管理 ==========
const showPermission = ref(false);
const permissionList = ref([]);
const openPermissionModal = async () => {
showPermission.value = true;
uni.showLoading({
title: '加载中...'
});
try {
const token = getToken();
// 如果有 teamId 则查团队权限,否则查用户权限(传空字符串后端自动识别当前用户)
let result;
if (teamId.value) {
result = await getCloudDbPermission(token, databaseId.value, 'team_id', teamId.value);
} else {
result = await getCloudDbPermission(token, databaseId.value, 'user_id', getUserId());
}
const rawList = Array.isArray(result) ? result : [];
// 归一化:根据数据中是否存在 team 对象或 team_id 字段来判断类型
permissionList.value = rawList.map(item => {
if (item.team) {
// team_id 返回格式:团队权限
return {
_id: item._id,
database_id: item.database_id,
access_level: item.access_level,
permType: 'team_id',
id: item.team._id || item.team.id,
name: item.team.name
};
} else if (item.team_id) {
// user_id 返回格式但有 team_id说明数据库归属团队显示为团队
return {
database_id: item.database_id,
access_level: item.access_level,
permType: 'team_id',
id: item.team_id,
name: item.database?.database_name || ''
};
} else {
// user_id 返回格式且无 team_id真正的个人数据库
return {
database_id: item.database_id,
access_level: item.access_level,
permType: 'user_id',
id: item.user_id || item.database?.user_id || getUserId(),
name: item.database?.database_name || ''
};
}
});
} catch (error) {
console.error('获取权限失败:', error);
permissionList.value = [];
}
uni.hideLoading();
};
const closePermissionModal = () => {
showPermission.value = false;
};
const changePermission = (perm, action) => {
if (action === 'delete') {
uni.showModal({
title: '移除权限',
content: `确定要移除"${perm.name}"的权限吗?`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: '移除中...'
});
try {
const token = getToken();
await deleteCloudDbPermission(token, databaseId.value, perm.permType, perm.id);
uni.hideLoading();
uni.showToast({
title: '已移除',
icon: 'success'
});
await openPermissionModal();
} catch (error) {
uni.hideLoading();
console.error('移除权限失败:', error);
uni.showToast({
title: '操作失败,请重试',
icon: 'error'
});
}
}
}
});
} else {
// 修改权限级别
const levels = ['无权限访问', '仅访问', '管理员完全控制', '团队完全控制'];
const currentLevel = perm.access_level || 0;
uni.showActionSheet({
title: '选择权限级别',
itemList: levels.map((label, idx) => `${idx}. ${label}`),
success: async (res) => {
const newLevel = res.tapIndex;
if (newLevel === currentLevel) return;
uni.showLoading({
title: '更新中...'
});
try {
const token = getToken();
await updateCloudDbPermission(token, databaseId.value, perm.permType, perm.id,
newLevel);
uni.hideLoading();
uni.showToast({
title: '已更新',
icon: 'success'
});
await openPermissionModal();
} catch (error) {
uni.hideLoading();
console.error('更新权限失败:', error);
uni.showToast({
title: '操作失败,请重试',
icon: 'error'
});
}
}
});
}
};
// ========== 移交团队 ==========
const showTransfer = ref(false);
const teamList = ref([]);
const selectedTeamId = ref('');
const openTransferModal = async () => {
showTransfer.value = true;
selectedTeamId.value = '';
try {
const token = getToken();
const teams = await getTeamList(token);
teamList.value = teams || [];
} catch (error) {
console.error('获取团队列表失败:', error);
teamList.value = [];
}
};
const closeTransferModal = () => {
showTransfer.value = false;
};
const confirmTransfer = async () => {
if (!selectedTeamId.value) {
uni.showToast({
title: '请选择目标团队',
icon: 'none'
});
return;
}
uni.showModal({
title: '确认移交',
content: '移交后数据库将归属于该团队,确定继续吗?',
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: '移交中...'
});
try {
const token = getToken();
await dbTransferToTeam(token, databaseId.value, selectedTeamId.value);
uni.hideLoading();
uni.showToast({
title: '移交成功',
icon: 'success'
});
closeTransferModal();
setTimeout(() => {
uni.navigateBack();
}, 1000);
} catch (error) {
uni.hideLoading();
console.error('移交失败:', error);
uni.showToast({
title: '移交失败,请重试',
icon: 'error'
});
}
}
}
});
};
// ========== 工具函数 ==========
const getAccessLevelLabel = (level) => {
const labels = {
0: '无权限访问',
1: '仅访问',
2: '管理员完全控制',
3: '团队完全控制'
};
return labels[level] || '未知';
};
const formatCellValue = (val) => {
if (val === null || val === undefined) return '';
if (typeof val === 'object') return JSON.stringify(val);
return String(val);
};
onMounted(() => {
// 从路由参数获取数据
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.$page?.options || currentPage.options || {};
databaseId.value = options.databaseId || '';
databaseName.value = decodeURIComponent(options.databaseName || '数据库详情');
teamId.value = options.teamId || '';
if (databaseId.value) {
fetchTables();
}
});
</script>
<style scoped>
@import url("CloudDbDetail.css");
</style>