第一次打包失败

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

@@ -850,4 +850,91 @@
color: #999;
margin-bottom: 20rpx;
text-align: center;
}
/* ===== 加载动画 ===== */
.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;
}
/* ===== 数据库标签 ===== */
.db-tag {
display: inline-block;
font-size: 20rpx;
font-weight: 500;
padding: 2rpx 12rpx;
border-radius: 8rpx;
margin-left: 10rpx;
vertical-align: middle;
}
.db-tag.tag-personal {
color: #3b86ff;
background: rgba(59, 134, 255, 0.08);
border: 1rpx solid rgba(59, 134, 255, 0.2);
}
.db-tag.tag-team {
color: #67c23a;
background: rgba(103, 194, 58, 0.08);
border: 1rpx solid rgba(103, 194, 58, 0.2);
}
/* ===== 上传数据库弹窗 ===== */
.upload-db-hint {
font-size: 22rpx;
color: #999;
margin-bottom: 20rpx;
text-align: center;
}
.upload-file-area {
padding: 40rpx;
border: 2rpx dashed #d0d0d0;
border-radius: 16rpx;
background: #f9fafb;
text-align: center;
margin-bottom: 20rpx;
}
.upload-file-area:active {
border-color: #3b86ff;
background: rgba(59, 134, 255, 0.03);
}
.selected-file {
font-size: 26rpx;
color: #3b86ff;
font-weight: 500;
word-break: break-all;
}
.upload-hint {
font-size: 24rpx;
color: #bbb;
}

View File

@@ -150,24 +150,39 @@
</view>
</view>
<!-- ===== 新建文件夹弹窗 ===== -->
<view v-if="showNewTable" class="popup-overlay" @click="closeNewTableModal">
<!-- ===== 新建数据库弹窗 ===== -->
<view v-if="showNewDb" class="popup-overlay" @click="closeNewDbModal">
<view class="popup-card" @click.stop>
<view class="close-popup-card" @click="closeNewTableModal">
<view class="close-popup-card" @click="closeNewDbModal">
<view class="iconfont icon-quxiao"></view>
</view>
<view class="popup-title">新建{{ currentCategory === 'user' ? '个人' : '团队' }}数据</view>
<view class="new-folder-name">名称</view>
<view class="popup-title">新建个人数据</view>
<view class="new-folder-name">数据库名称</view>
<view class="create-folder-name">
<input v-model="newTableName" type="text" placeholder="输入表名称users" />
</view>
<view class="new-folder-name">描述</view>
<view class="create-folder-name">
<input v-model="newTableDesc" type="text" placeholder="输入表描述(可选)" />
<input v-model="newDbName" type="text" placeholder="输入数据库名称" />
</view>
<view class="option-wrapper">
<view class="opt-btn opt-cancel" @click="closeNewTableModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmCreateTable">确认</view>
<view class="opt-btn opt-cancel" @click="closeNewDbModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmCreateDb">确认</view>
</view>
</view>
</view>
<!-- ===== 上传数据库文件弹窗 ===== -->
<view v-if="showUploadDb" class="popup-overlay" @click="closeUploadDbModal">
<view class="popup-card" @click.stop>
<view class="close-popup-card" @click="closeUploadDbModal">
<view class="iconfont icon-quxiao"></view>
</view>
<view class="popup-title">上传数据库文件</view>
<view class="upload-db-hint">支持 .db / .sqlite / .sqlite3 格式的 SQLite 数据库文件</view>
<view class="upload-file-area" @click="handlePickDbFile">
<view v-if="uploadDbFileName" class="selected-file">{{ uploadDbFileName }}</view>
<view v-else class="upload-hint">点击选择数据库文件</view>
</view>
<view class="option-wrapper">
<view class="opt-btn opt-cancel" @click="closeUploadDbModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmUploadDb">确认上传</view>
</view>
</view>
</view>
@@ -182,15 +197,15 @@
</view>
<view class="navbar-title">{{ navbarTitle }}</view>
<view class="navbar-right">
<view v-if="isSelectFolder" class="navbar-right-text" @click="handleSelectFolder">完成</view>
<!-- <view v-if="isSelectFolder" class="navbar-right-text" @click="handleSelectFolder">完成</view>
<view v-else class="iconfont icon-gengduo" :class="isMenuOpen ? 'menu-open' : 'custom-navbar-icon'"
@click="handleMenu"></view>
@click="handleMenu"></view> -->
</view>
<!-- <view v-if="isMenuOpen" class="menu-card">
<view v-if="isMenuOpen" class="menu-card">
<view class="menu-card-item" @click="handleSelectFolder(); handleMenu()">选择</view>
<view class="solid-line"></view>
<view class="menu-card-item" @click="openNewFolderModal(); handleMenu()">新建文件夹</view>
</view> -->
</view>
</view>
<!-- 个人/团队 分类切换 -->
@@ -258,31 +273,46 @@
<view class="section-title">
<view class="iconfont icon-shujuku"></view>
<text>云端数据库</text>
<text class="file-count">{{ !isDbLoading && personalDatabases.length > 0 ? '(' + personalDatabases.length + ')' : '' }}</text>
</view>
<view class="section-actions">
<view class="action-btn" @click="openNewTableModal">
<view class="action-btn" @click="openUploadDbModal">
<uni-icons type="upload" size="16"></uni-icons>
<text>上传数据库</text>
</view>
<view class="action-btn" @click="openNewDbModal">
<uni-icons type="plus" size="16"></uni-icons>
<text>新建</text>
<text>新建数据库</text>
</view>
</view>
</view>
<view class="section-body">
<view v-if="personalTables.length === 0" class="empty-tip">
<!-- 加载中 -->
<view v-if="isDbLoading" class="loading-area">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="personalDatabases.length === 0" class="empty-tip">
<view class="iconfont icon-shujuku"></view>
<text>暂无数据</text>
<text>暂无数据</text>
</view>
<view v-else class="table-list">
<view class="table-item" v-for="table in personalTables" :key="table.id"
@click="handleTableClick(table)">
<view class="table-item" v-for="db in personalDatabases" :key="db.database_id"
@click="handleDbClick(db)" @longpress="handleDeleteDb(db)">
<view class="table-icon">
<uni-icons type="compose" size="22" color="#3b86ff"></uni-icons>
</view>
<view class="table-info">
<view class="table-name">{{ table.name }}</view>
<view class="table-desc">{{ table.desc || '无描述' }}</view>
<view class="table-name">
{{ db.database_name }}
<text class="db-tag" :class="db.team_id ? 'tag-team' : 'tag-personal'">
{{ db.team_id ? '团队共享' : '个人私有' }}
</text>
</view>
<view class="table-desc">{{ db.table_count || 0 }} 张表 · {{ getAccessLevelLabel(db.access_level) }}</view>
</view>
<view class="table-meta">
<text class="table-rows">{{ table.rowCount || 0 }} 条记录</text>
<text class="table-rows">{{ formatDbTime(db.modified_time) }}</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
</view>
</view>
@@ -376,30 +406,32 @@
<view class="iconfont icon-shujuku"></view>
<text>团队云端数据库</text>
</view>
<view class="section-actions">
<view class="action-btn" @click="openNewTableModal">
<uni-icons type="plus" size="16"></uni-icons>
<text>新建表</text>
</view>
</view>
</view>
<view class="section-body">
<view v-if="teamTables.length === 0" class="empty-tip">
<!-- 加载中 -->
<view v-if="isDbLoading" class="loading-area">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="teamDatabases.length === 0" class="empty-tip">
<view class="iconfont icon-shujuku"></view>
<text>暂无团队数据</text>
<text>暂无团队数据</text>
</view>
<view v-else class="table-list">
<view class="table-item" v-for="table in teamTables" :key="table.id"
@click="handleTableClick(table)">
<view class="table-item" v-for="db in teamDatabases" :key="db.database_id"
@click="handleDbClick(db)">
<view class="table-icon">
<uni-icons type="compose" size="22" color="#3b86ff"></uni-icons>
</view>
<view class="table-info">
<view class="table-name">{{ table.name }}</view>
<view class="table-desc">{{ table.desc || '无描述' }}</view>
<view class="table-name">
{{ db.database_name }}
<text class="db-tag tag-team">团队共享</text>
</view>
<view class="table-desc">{{ db.table_count || 0 }} 张表 · {{ getAccessLevelLabel(db.access_level) }}</view>
</view>
<view class="table-meta">
<text class="table-rows">{{ table.rowCount || 0 }} 条记录</text>
<text class="table-rows">{{ formatDbTime(db.modified_time) }}</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
</view>
</view>
@@ -420,6 +452,7 @@
import {
getToken
} from '@/utils/user-info.js';
import { chooseFile } from '@/uni_modules/lime-choose-file';
import {
getUserInfo,
getCloudFileList,
@@ -436,7 +469,11 @@
removeTeamMember,
uploadCloudFile,
deleteCloudFile,
deleteTeam
deleteTeam,
getCloudDbList,
createCloudDb,
uploadCloudDbFile,
deleteCloudDb
} from '@/utils/cloud-api.js';
const userToken = ref('');
@@ -1075,31 +1112,7 @@
// 选择文件并上传
const chooseAndUploadFile = () => {
// #ifdef APP-PLUS
// App 端使用系统文件选择器
plus.io.chooseFile(
(res) => {
// plus.io.chooseFile 返回格式可能是 { files: [...] } 或直接是数组
const files = Array.isArray(res) ? res : (res.files || []);
if (files.length === 0) {
uni.showToast({
title: '未选择文件',
icon: 'none'
});
return;
}
doUploadFiles(files);
},
(err) => {
if (err.code !== 11) { // 11 是用户取消
console.error('选择文件失败:', err);
}
}
);
// #endif
// #ifndef APP-PLUS
// 小程序/H5 端使用 uni.chooseFileH5 可能不支持)
uni.chooseFile({
chooseFile({
count: 10,
type: 'all',
success: (res) => {
@@ -1120,7 +1133,6 @@
console.error('选择文件失败:', err);
}
});
// #endif
};
// 执行上传
@@ -1152,88 +1164,160 @@
}
};
// ========== 数据库 ==========
const personalTables = ref([{
id: '1',
name: 'users',
desc: '用户信息表',
rowCount: 128
},
{
id: '2',
name: 'messages',
desc: '聊天记录表',
rowCount: 2048
},
{
id: '3',
name: 'settings',
desc: '用户配置表',
rowCount: 56
}
]);
const teamTables = ref([{
id: 't1',
name: 'team_tasks',
desc: '团队任务表',
rowCount: 32
},
{
id: 't2',
name: 'team_docs',
desc: '团队文档表',
rowCount: 15
}
]);
// ========== 云端数据库 ==========
const allDatabases = ref([]); // API 返回的全部数据库列表
const isDbLoading = ref(false); // 数据库加载中
// 个人展示全部数据库(个人私有 + 归属团队的),团队只展示属于当前选中团队的
const personalDatabases = computed(() => allDatabases.value);
const teamDatabases = computed(() => allDatabases.value.filter(db => db.team_id && String(db.team_id) === String(currentTeamId.value)));
const showNewTable = ref(false);
const newTableName = ref('');
const newTableDesc = ref('');
const showNewDb = ref(false);
const newDbName = ref('');
const openNewTableModal = () => {
showNewTable.value = true;
newTableName.value = '';
newTableDesc.value = '';
// 获取数据库列表
const fetchDbList = async () => {
isDbLoading.value = true;
try {
const token = getToken();
const list = await getCloudDbList(token);
allDatabases.value = list || [];
} catch (error) {
console.error('获取数据库列表失败:', error);
allDatabases.value = [];
} finally {
isDbLoading.value = false;
}
};
const closeNewTableModal = () => {
showNewTable.value = false;
newTableName.value = '';
newTableDesc.value = '';
const openNewDbModal = () => {
showNewDb.value = true;
newDbName.value = '';
};
const confirmCreateTable = () => {
const name = newTableName.value.trim();
const closeNewDbModal = () => {
showNewDb.value = false;
newDbName.value = '';
};
const confirmCreateDb = async () => {
const name = newDbName.value.trim();
if (!name) {
uni.showToast({
title: '请输入表名称',
icon: 'none'
});
uni.showToast({ title: '请输入数据库名称', icon: 'none' });
return;
}
const newTable = {
id: Date.now().toString(),
name: name,
desc: newTableDesc.value.trim() || '无描述',
rowCount: 0
};
if (currentCategory.value === 'user') {
personalTables.value.push(newTable);
} else {
teamTables.value.push(newTable);
uni.showLoading({ title: '创建中...' });
try {
const token = getToken();
await createCloudDb(token, name);
uni.hideLoading();
uni.showToast({ title: '创建成功', icon: 'success' });
closeNewDbModal();
await fetchDbList();
} catch (error) {
uni.hideLoading();
console.error('创建数据库失败:', error);
uni.showToast({ title: '创建失败,请重试', icon: 'error' });
}
uni.showToast({
title: '创建成功',
icon: 'success'
});
closeNewTableModal();
};
const handleTableClick = (table) => {
uni.showToast({
title: `进入数据表:${table.name}`,
icon: 'none'
// ========== 上传数据库文件 ==========
const showUploadDb = ref(false);
const uploadDbFileName = ref('');
const uploadDbFilePath = ref('');
const openUploadDbModal = () => {
showUploadDb.value = true;
uploadDbFileName.value = '';
uploadDbFilePath.value = '';
};
const closeUploadDbModal = () => {
showUploadDb.value = false;
};
const handlePickDbFile = () => {
chooseFile({
count: 1,
type: 'all',
success: (res) => {
const file = res.tempFiles[0];
uploadDbFileName.value = file.name || 'unknown';
uploadDbFilePath.value = file.path;
},
fail: (err) => {
console.error('选择文件失败:', err);
uni.showToast({ title: '选择文件失败', icon: 'none' });
}
});
};
const confirmUploadDb = async () => {
if (!uploadDbFilePath.value) {
uni.showToast({ title: '请选择数据库文件', icon: 'none' });
return;
}
// 校验文件后缀
const ext = uploadDbFileName.value.split('.').pop()?.toLowerCase();
const allowedExts = ['db', 'sqlite', 'sqlite3'];
if (!allowedExts.includes(ext)) {
uni.showToast({ title: '仅支持 .db / .sqlite / .sqlite3 文件', icon: 'none' });
return;
}
uni.showLoading({ title: '上传中...' });
try {
const token = getToken();
await uploadCloudDbFile(token, uploadDbFilePath.value);
uni.hideLoading();
uni.showToast({ title: '上传成功', icon: 'success' });
closeUploadDbModal();
await fetchDbList();
} catch (error) {
uni.hideLoading();
console.error('上传数据库失败:', error);
uni.showToast({ title: '上传失败,请重试', icon: 'error' });
}
};
const handleDeleteDb = (db) => {
uni.showModal({
title: '删除数据库',
content: `确定要删除数据库"${db.database_name}"吗?此操作不可恢复。`,
success: async (res) => {
if (res.confirm) {
uni.showLoading({ title: '删除中...' });
try {
const token = getToken();
await deleteCloudDb(token, db.database_id);
uni.hideLoading();
uni.showToast({ title: '删除成功', icon: 'success' });
await fetchDbList();
} catch (error) {
uni.hideLoading();
console.error('删除数据库失败:', error);
uni.showToast({ title: '删除失败,请重试', icon: 'error' });
}
}
}
});
};
const handleDbClick = (db) => {
uni.navigateTo({
url: `/pages/CloudDbDetail/CloudDbDetail?databaseId=${db.database_id}&databaseName=${encodeURIComponent(db.database_name)}&teamId=${db.team_id || ''}`
});
};
const formatDbTime = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
};
const getAccessLevelLabel = (level) => {
const labels = { 0: '无权限', 1: '只读', 2: '读写', 3: '管理' };
return labels[level] || '未知';
};
// ========== 团队管理 ==========
const showTeamManage = ref(false);
const teamGroups = ref([]);
@@ -1568,6 +1652,7 @@
onMounted(async () => {
await fetchFileList();
await fetchTeamList();
await fetchDbList();
});
</script>