Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
<template>
<view class="migration-container">
<view class="page-header">
<text class="page-title">数据库迁移工具</text>
<text class="page-subtitle">评分标准JSON结构化迁移</text>
</view>
<scroll-view class="scroll-container" direction="vertical">
<view class="content-wrapper">
<!-- 迁移状态 -->
<view class="status-section">
<view class="section-title">迁移状态</view>
<view class="status-item" :class="{ 'success': migrationCompleted, 'pending': !migrationCompleted }">
<text class="status-text">{{ migrationStatus }}</text>
</view>
</view>
<!-- 迁移操作 -->
<view class="action-section">
<view class="section-title">执行迁移</view>
<button @click="executeMigration" class="migration-btn" :disabled="isExecuting">
<text>{{ isExecuting ? '执行中...' : '执行评分标准JSON迁移' }}</text>
</button>
<text class="help-text">此操作将把评分标准从字符串格式迁移到JSON格式</text>
</view>
<!-- 验证操作 -->
<view class="action-section">
<view class="section-title">验证结果</view>
<button @click="validateMigration" class="validate-btn" :disabled="isValidating">
<text>{{ isValidating ? '验证中...' : '验证迁移结果' }}</text>
</button>
<text class="help-text">检查迁移是否成功完成</text>
</view>
<!-- 结果显示 -->
<view v-if="migrationResult" class="result-section">
<view class="section-title">执行结果</view>
<view class="result-content">
<text class="result-text">{{ migrationResult }}</text>
</view>
</view>
<!-- 验证结果 -->
<view v-if="validationResult" class="result-section">
<view class="section-title">验证结果</view>
<view class="result-content">
<text class="result-text">{{ validationResult }}</text>
</view>
</view>
<!-- 警告信息 -->
<view class="warning-section">
<text class="warning-title">⚠️ 重要说明</text>
<text class="warning-text">• 迁移会自动创建备份表</text>
<text class="warning-text">• 确保在非生产环境先测试</text>
<text class="warning-text">• 迁移完成后可删除此页面</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
export default {
data() {
return {
isExecuting: false,
isValidating: false,
migrationCompleted: false,
migrationStatus: '等待执行迁移',
migrationResult: '',
validationResult: ''
}
},
methods: {
async executeMigration() {
this.isExecuting = true
this.migrationResult = ''
this.migrationStatus = '正在执行迁移...'
try {
// 执行迁移SQL脚本
const migrationSQL = `
-- 步骤1创建备份表
CREATE TABLE IF NOT EXISTS ak_training_projects_backup_20250611 AS
SELECT * FROM ak_training_projects;
-- 步骤2确保 scoring_criteria 字段为 JSONB 类型
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'ak_training_projects'
AND column_name = 'scoring_criteria'
AND data_type != 'jsonb'
) THEN
ALTER TABLE ak_training_projects
ALTER COLUMN scoring_criteria TYPE JSONB
USING CASE
WHEN scoring_criteria IS NULL OR scoring_criteria = '' THEN NULL
WHEN scoring_criteria ~ '^[\\s\\t\\n\\r]*\\{.*\\}[\\s\\t\\n\\r]*$' THEN scoring_criteria::JSONB
ELSE jsonb_build_object('legacy_text', scoring_criteria)
END;
END IF;
END $$;
-- 步骤3为没有评分标准的项目添加默认JSON结构
UPDATE ak_training_projects
SET scoring_criteria = jsonb_build_object(
'criteria', jsonb_build_array(
jsonb_build_object(
'min_score', 90,
'max_score', 100,
'description', '优秀:表现卓越,超出预期'
),
jsonb_build_object(
'min_score', 80,
'max_score', 89,
'description', '良好:表现良好,符合要求'
),
jsonb_build_object(
'min_score', 70,
'max_score', 79,
'description', '及格:基本达标,有待改进'
),
jsonb_build_object(
'min_score', 0,
'max_score', 69,
'description', '不及格:未达标准,需要重练'
)
),
'scoring_method', 'comprehensive',
'weight_distribution', jsonb_build_object(
'technique', 0.4,
'effort', 0.3,
'improvement', 0.3
)
)
WHERE scoring_criteria IS NULL
OR scoring_criteria = '{}'
OR jsonb_typeof(scoring_criteria) != 'object'
OR NOT (scoring_criteria ? 'criteria');
-- 步骤4创建索引
CREATE INDEX IF NOT EXISTS idx_ak_training_projects_scoring_criteria
ON ak_training_projects USING GIN (scoring_criteria);
`
// 使用RPC执行SQL
const result = await supa.rpc('exec_sql', { sql: migrationSQL })
if (result.status >= 200 && result.status < 300) {
this.migrationCompleted = true
this.migrationStatus = '迁移执行完成'
this.migrationResult = '✅ 评分标准JSON迁移执行成功\n\n已完成\n• 创建备份表\n• 转换字段类型为JSONB\n• 添加默认评分标准\n• 创建性能索引'
uni.showToast({
title: '迁移成功',
icon: 'success'
})
} else {
throw new Error(result.error?.message ?? '迁移执行失败')
}
} catch (error) {
console.error('迁移执行失败:', error)
this.migrationStatus = '迁移执行失败'
this.migrationResult = `❌ 迁移执行失败:${error}\n\n请手动在数据库中执行 migrate_scoring_criteria_simple.sql 脚本`
uni.showToast({
title: '迁移失败',
icon: 'error'
})
} finally {
this.isExecuting = false
}
},
async validateMigration() {
this.isValidating = true
this.validationResult = ''
try {
// 验证迁移结果
const validationSQL = `SELECT
COUNT(*) as total_projects,
COUNT(CASE WHEN scoring_criteria ? 'criteria' THEN 1 END) as json_format_count,
COUNT(CASE WHEN jsonb_typeof(scoring_criteria) = 'object' THEN 1 END) as valid_json_count
FROM ak_training_projects;`
const result = await supa.rpc('exec_sql', { sql: validationSQL })
const status = result['status'] as number | null
if (status != null && status >= 200 && status < 300 && result['data'] != null) {
const data = Array.isArray(result['data']) ? (result['data'] as Array<UTSJSONObject>)[0] : result['data'] as UTSJSONObject
const totalProjects = (data['total_projects'] as number) ?? 0
const jsonFormatCount = (data['json_format_count'] as number) ?? 0
const validJsonCount = (data['valid_json_count'] as number) ?? 0
this.validationResult = ` 验证结果:\n\n• 总项目数:${totalProjects}\n• JSON格式项目${jsonFormatCount}\n• 有效JSON结构${validJsonCount}\n\n${
jsonFormatCount === totalProjects && validJsonCount === totalProjects
? '✅ 所有项目都已成功迁移到JSON格式'
: '⚠️ 部分项目可能需要检查'
}`
// 获取示例数据
const exampleSQL = `
SELECT title, scoring_criteria->'criteria' as criteria
FROM ak_training_projects
WHERE scoring_criteria IS NOT NULL
LIMIT 2;
`
const exampleResult = await supa.rpc('exec_sql', { sql: exampleSQL }) as UTSJSONObject
const exampleStatus = exampleResult['status'] as number | null
if (exampleStatus != null && exampleStatus >= 200 && exampleStatus < 300 && exampleResult['data'] != null) {
this.validationResult += '\n\n 示例数据:\n'
const examples = Array.isArray(exampleResult['data']) ? (exampleResult['data'] as Array<UTSJSONObject>) : [exampleResult['data'] as UTSJSONObject]
examples.forEach((item: UTSJSONObject, index: number) => {
const title = item['title'] as string
const criteria = item['criteria'] as string
this.validationResult += `\n${index + 1}. ${title}\n 评分标准:${JSON.stringify(criteria, null, 2)}\n`
})
}
} else {
throw new Error('验证查询失败')
}
} catch (error) {
console.error('验证失败:', error)
this.validationResult = `❌ 验证失败:${error}\n\n请手动检查数据库中的 ak_training_projects 表`
} finally {
this.isValidating = false
}
}
}
}
</script>
<style scoped>
.migration-container {
flex: 1;
background-image: linear-gradient(to bottom right, #667eea, #764ba2);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.page-header {
padding: 40rpx 30rpx 30rpx;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
}
.page-title {
font-size: 48rpx;
font-weight: bold;
color: #FFFFFF;
margin-bottom: 10rpx;
}
.page-subtitle {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.scroll-container {
flex: 1;
}
.content-wrapper {
padding: 30rpx;
background: #F8FAFC;
border-radius: 30rpx 30rpx 0 0;
margin-top: 20rpx;
min-height: calc(100vh - 160rpx);
}
.status-section,
.action-section,
.result-section,
.warning-section {
background: #FFFFFF;
padding: 30rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
margin-bottom: 30rpx;
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #1E293B;
margin-bottom: 20rpx;
}
.status-item {
padding: 20rpx;
border-radius: 15rpx;
text-align: center;
}
.status-item.pending {
background: #FEF3C7;
border: 2rpx solid #F59E0B;
}
.status-item.success {
background: #D1FAE5;
border: 2rpx solid #10B981;
}
.status-text {
font-size: 28rpx;
font-weight: 400;
color: #374151;
}
.migration-btn,
.validate-btn {
width: 100%;
height: 80rpx;
border-radius: 15rpx;
font-size: 32rpx;
font-weight: 400;
border: none;
margin-bottom: 15rpx;
}
.migration-btn {
background-image: linear-gradient(to bottom right, #6366F1, #8B5CF6);
color: #FFFFFF;
}
.validate-btn {
background-image: linear-gradient(to bottom right, #10B981, #059669);
color: #FFFFFF;
}
.migration-btn:disabled,
.validate-btn:disabled {
opacity: 0.5;
}
.help-text {
font-size: 24rpx;
color: #6B7280;
text-align: center;
}
.result-content {
background: #F9FAFB;
padding: 20rpx;
border-radius: 15rpx;
border: 2rpx solid #E5E7EB;
}
.result-text {
font-size: 26rpx;
color: #374151;
line-height: 1.6;
white-space: pre-line;
}
.warning-title {
font-size: 32rpx;
font-weight: bold;
color: #F59E0B;
margin-bottom: 15rpx;
}
.warning-text {
font-size: 26rpx;
color: #6B7280;
margin-bottom: 10rpx;
line-height: 1.5;
}
</style>