Compare commits

...

2 Commits

Author SHA1 Message Date
En
7bc8c10d9a feat: 系统品牌更名为"智创未来"及相关配置调整
- 前后端所有"乐绘世界"统一更名为"智创未来"
- 生产环境乐读派API地址更新为公网地址
- 公众端登录页调整用户名/密码字段显示逻辑
- 同步更新文档、测试用例、主题样式中的品牌名称

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 19:30:26 +08:00
En
ffb5c449fb fix(乐读派): B3对账时B2详情接口缺少phone字段导致无法关联用户
B3批量查询返回phone,但调用B2获取详情后phone丢失。
将B3列表中的phone合并到B2详情数据中再传给syncWork。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 19:28:58 +08:00
27 changed files with 39 additions and 37 deletions

View File

@ -22,7 +22,7 @@
升级后:绘本创作社区 + 活动平台(高频、主动) 升级后:绘本创作社区 + 活动平台(高频、主动)
┌──────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────┐
乐绘世界 用户端 │ 智创未来 用户端 │
│ │ │ │
│ 创作工具 → 作品库 → 发布广场 → 社区互动 │ │ 创作工具 → 作品库 → 发布广场 → 社区互动 │
│ ↓ │ │ ↓ │

View File

@ -2,7 +2,7 @@
## 1. 项目背景 ## 1. 项目背景
广东省立中山图书馆(以下简称"广东省图")计划举办一场面向全国的少儿绘本创作活动。乐绘世界创想活动乐园作为活动管理平台,需要支撑从活动发布、用户注册、报名参与、作品提交、评审打分到成果发布的完整流程。 广东省立中山图书馆(以下简称"广东省图")计划举办一场面向全国的少儿绘本创作活动。智创未来作为活动管理平台,需要支撑从活动发布、用户注册、报名参与、作品提交、评审打分到成果发布的完整流程。
### 1.1 业务场景 ### 1.1 业务场景

View File

@ -49,7 +49,7 @@
| 类型标识 | 名称 | 说明 | 举例 | | 类型标识 | 名称 | 说明 | 举例 |
|---------|------|------|------| |---------|------|------|------|
| `platform` | 平台租户 | 系统运营方,全局唯一 | 乐绘世界 | | `platform` | 平台租户 | 系统运营方,全局唯一 | 智创未来 |
| `library` | 图书馆 | 公共图书馆机构 | 广东省立中山图书馆 | | `library` | 图书馆 | 公共图书馆机构 | 广东省立中山图书馆 |
| `kindergarten` | 幼儿园 | 幼儿教育机构 | XX幼儿园 | | `kindergarten` | 幼儿园 | 幼儿教育机构 | XX幼儿园 |
| `school` | 学校 | 中小学校 | XX小学 | | `school` | 学校 | 中小学校 | XX小学 |
@ -173,7 +173,7 @@
``` ```
┌────────────────────────────────────────┐ Web 端 ┌────────────────────────────────────────┐ Web 端
│ 顶部导航栏 │ │ 顶部导航栏 │
│ [Logo 乐绘世界] [活动大厅] [个人中心] │ │ [Logo 智创未来] [活动大厅] [个人中心] │
├────────────────────────────────────────┤ ├────────────────────────────────────────┤
│ │ │ │
│ 页面内容区 │ │ 页面内容区 │

View File

@ -117,7 +117,7 @@ CREATE INDEX idx_tenants_type ON tenants(tenant_type);
| 值 | 说明 | | 值 | 说明 |
|----|------| |----|------|
| `platform` | 平台租户(乐绘世界,全局唯一) | | `platform` | 平台租户(智创未来,全局唯一) |
| `library` | 图书馆 | | `library` | 图书馆 |
| `kindergarten` | 幼儿园 | | `kindergarten` | 幼儿园 |
| `school` | 学校 | | `school` | 学校 |

View File

@ -48,7 +48,7 @@
- "比赛/赛事" → "活动"(前后端 46 个文件 + 数据库) - "比赛/赛事" → "活动"(前后端 46 个文件 + 数据库)
- "个人赛/团队赛" → "个人参与/团队参与"25 个文件 + 数据库) - "个人赛/团队赛" → "个人参与/团队参与"25 个文件 + 数据库)
- "赛果" → "成果"25 个文件 + 数据库菜单/权限) - "赛果" → "成果"25 个文件 + 数据库菜单/权限)
- 系统名称 → "乐绘世界创想活动乐园" - 系统名称 → "智创未来"
- Logo 替换为 LeSingle 品牌标志 - Logo 替换为 LeSingle 品牌标志
**UI 主题全面改造6 个核心文件):** **UI 主题全面改造6 个核心文件):**

View File

@ -2,7 +2,7 @@
## 1. 系统改名记录 ## 1. 系统改名记录
本系统从"比赛管理系统"改造为"乐绘世界创想活动乐园"。以下为所有术语变更对照。 本系统从"比赛管理系统"改造为"智创未来"。以下为所有术语变更对照。
### 1.1 核心术语 ### 1.1 核心术语
@ -10,8 +10,8 @@
|--------|--------|------| |--------|--------|------|
| 比赛 | 活动 | 全局替换 | | 比赛 | 活动 | 全局替换 |
| 赛事 | 活动 | 全局替换 | | 赛事 | 活动 | 全局替换 |
| 比赛管理系统 | 乐绘世界创想活动乐园 | 系统名称 | | 比赛管理系统 | 智创未来 | 系统名称 |
| CMS | 乐绘世界 | 简称 | | CMS | 智创未来 | 简称 |
### 1.2 活动类型 ### 1.2 活动类型
@ -57,6 +57,6 @@
|------|---------| |------|---------|
| 2026-03-24 | 所有"比赛""赛事"替换为"活动"(前后端代码+数据库) | | 2026-03-24 | 所有"比赛""赛事"替换为"活动"(前后端代码+数据库) |
| 2026-03-24 | "个人赛"→"个人参与""团队赛"→"团队参与""赛果"→"成果" | | 2026-03-24 | "个人赛"→"个人参与""团队赛"→"团队参与""赛果"→"成果" |
| 2026-03-24 | 系统名称改为"乐绘世界创想活动乐园" | | 2026-03-24 | 系统名称改为"智创未来" |
| 2026-03-24 | Logo 替换为 LeSingle 品牌标志 | | 2026-03-24 | Logo 替换为 LeSingle 品牌标志 |
| 2026-03-24 | UI 主题全面升级Creative Indigo 主色调、Nunito 字体、圆角化组件 | | 2026-03-24 | UI 主题全面升级Creative Indigo 主色调、Nunito 字体、圆角化组件 |

View File

@ -2,11 +2,11 @@
## 1. 系统架构概览 ## 1. 系统架构概览
乐绘世界创想活动乐园采用**多端 + 多租户**架构,共分为 3 个端: 智创未来采用**多端 + 多租户**架构,共分为 3 个端:
``` ```
┌──────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────┐
乐绘世界创想活动乐园 智创未来
├──────────┬───────────────────┬───────────────────────────┤ ├──────────┬───────────────────┬───────────────────────────┤
│ 公众端 │ 机构管理端 │ 平台管理端(超管) │ │ 公众端 │ 机构管理端 │ 平台管理端(超管) │
│ /p/... │ /:tenantCode/... │ /super/... │ │ /p/... │ /:tenantCode/... │ /super/... │

View File

@ -18,7 +18,7 @@
## 项目背景 ## 项目背景
乐绘世界创想活动乐园(原比赛管理系统)承办广东省立中山图书馆面向全国的少儿绘本创作活动。该项目需要将原有的机构内部管理系统改造为**面向公众开放**的活动管理平台。 智创未来(原比赛管理系统)承办广东省立中山图书馆面向全国的少儿绘本创作活动。该项目需要将原有的机构内部管理系统改造为**面向公众开放**的活动管理平台。
## 核心改造方向 ## 核心改造方向

View File

@ -68,6 +68,8 @@ public class LeaiReconcileTask {
Map<String, Object> fullData = leaiApiClient.fetchWorkDetail(workId); Map<String, Object> fullData = leaiApiClient.fetchWorkDetail(workId);
if (fullData != null) { if (fullData != null) {
// B2 详情接口不返回 phone B3 列表中的 phone 合并进去
fullData.put("phone", phone);
try { try {
leaiSyncService.syncWork(workId, fullData, "B3对账"); leaiSyncService.syncWork(workId, fullData, "B3对账");
synced++; synced++;

View File

@ -19,7 +19,7 @@ public class SmsConfig {
private String accessKeySecret; private String accessKeySecret;
/** 短信签名名称 */ /** 短信签名名称 */
private String signName = "乐绘世界"; private String signName = "智创未来";
/** 验证码模板 CODE */ /** 验证码模板 CODE */
private String templateCode; private String templateCode;

View File

@ -51,7 +51,7 @@ jwt:
leai: leai:
org-id: ${LEAI_ORG_ID:gdlib} org-id: ${LEAI_ORG_ID:gdlib}
app-secret: ${LEAI_APP_SECRET:leai_mnoi9q1a_mtcawrn8y} app-secret: ${LEAI_APP_SECRET:leai_mnoi9q1a_mtcawrn8y}
api-url: ${LEAI_API_URL:http://192.168.1.250:8267} api-url: ${LEAI_API_URL:http://121.40.20.224:8267}
reconcile-interval: 1800000 # B3对账间隔30分钟生产环境 reconcile-interval: 1800000 # B3对账间隔30分钟生产环境
reconcile-initial-delay: 60000 # 初始延迟60秒 reconcile-initial-delay: 60000 # 初始延迟60秒

View File

@ -15,7 +15,7 @@ test.describe('管理端登录流程', () => {
await page.goto(`/${TENANT_CODE}/login`) await page.goto(`/${TENANT_CODE}/login`)
// 验证页面标题 // 验证页面标题
await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') await expect(page.locator('.login-header h2')).toHaveText('智创未来')
// 验证表单字段可见 // 验证表单字段可见
await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -9,7 +9,7 @@ test.describe('侧边栏导航', () => {
const page = adminPage const page = adminPage
// 验证侧边栏 Logo 区域 // 验证侧边栏 Logo 区域
await expect(page.locator('.logo-title-main')).toHaveText('乐绘世界') await expect(page.locator('.logo-title-main')).toHaveText('智创未来')
// 验证菜单项存在Ant Design 菜单项) // 验证菜单项存在Ant Design 菜单项)
const menuItems = page.locator('.ant-menu-item, .ant-menu-submenu') const menuItems = page.locator('.ant-menu-item, .ant-menu-submenu')

View File

@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<title>乐绘世界创想活动乐园</title> <title>智创未来</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -15,7 +15,7 @@
Error: expect(locator).toHaveText(expected) failed Error: expect(locator).toHaveText(expected) failed
Locator: locator('.login-header h2') Locator: locator('.login-header h2')
Expected: "乐绘世界创想活动乐园" Expected: "智创未来"
Timeout: 10000ms Timeout: 10000ms
Error: element(s) not found Error: element(s) not found
@ -45,7 +45,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
> 18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') > 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
| ^ Error: expect(locator).toHaveText(expected) failed | ^ Error: expect(locator).toHaveText(expected) failed
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见

View File

@ -38,7 +38,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见
21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -38,7 +38,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见
21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -38,7 +38,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见
21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -38,7 +38,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见
21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -38,7 +38,7 @@ Call log:
15 | await page.goto(`/${TENANT_CODE}/login`) 15 | await page.goto(`/${TENANT_CODE}/login`)
16 | 16 |
17 | // 验证页面标题 17 | // 验证页面标题
18 | await expect(page.locator('.login-header h2')).toHaveText('乐绘世界创想活动乐园') 18 | await expect(page.locator('.login-header h2')).toHaveText('智创未来')
19 | 19 |
20 | // 验证表单字段可见 20 | // 验证表单字段可见
21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible() 21 | await expect(page.locator('input[placeholder="请输入用户名"]')).toBeVisible()

View File

@ -12,7 +12,7 @@ import type { Locale } from "ant-design-vue/es/locale"
// ant-design-vue // ant-design-vue
const locale: Locale = zhCN const locale: Locale = zhCN
// //
// //
const themeConfig: ConfigProviderProps["theme"] = { const themeConfig: ConfigProviderProps["theme"] = {
token: { token: {

View File

@ -9,9 +9,9 @@
<div class="sider-content"> <div class="sider-content">
<div class="sider-top"> <div class="sider-top">
<div class="logo" :class="{ 'logo-collapsed': collapsed }"> <div class="logo" :class="{ 'logo-collapsed': collapsed }">
<img src="../assets/images/logo-icon.png" alt="乐绘世界" class="logo-img" /> <img src="../assets/images/logo-icon.png" alt="智创未来" class="logo-img" />
<div v-if="!collapsed" class="logo-text"> <div v-if="!collapsed" class="logo-text">
<span class="logo-title-main">乐绘世界</span> <span class="logo-title-main">智创未来</span>
<span class="logo-title-sub">创想活动乐园</span> <span class="logo-title-sub">创想活动乐园</span>
</div> </div>
</div> </div>

View File

@ -4,8 +4,8 @@
<header class="public-header"> <header class="public-header">
<div class="header-inner"> <div class="header-inner">
<div class="header-brand" @click="goHome"> <div class="header-brand" @click="goHome">
<img src="@/assets/images/logo-icon.png" alt="乐绘世界" class="header-logo" /> <img src="@/assets/images/logo-icon.png" alt="智创未来" class="header-logo" />
<span class="header-title">乐绘世界</span> <span class="header-title">智创未来</span>
</div> </div>
<!-- 桌面端导航菜单 --> <!-- 桌面端导航菜单 -->
<nav class="header-nav"> <nav class="header-nav">

View File

@ -1,4 +1,4 @@
// 乐绘世界创想活动乐园 主题定制 // 智创未来 主题定制
// 风格活泼艺术少儿绘本创作 // 风格活泼艺术少儿绘本创作
// 主色调Creative Indigo#6366F1 激发创造力与想象力 // 主色调Creative Indigo#6366F1 激发创造力与想象力

View File

@ -10,8 +10,8 @@
<div class="login-card"> <div class="login-card">
<div class="login-header"> <div class="login-header">
<img src="@/assets/images/logo-icon.png" alt="乐绘世界" class="login-logo" /> <img src="@/assets/images/logo-icon.png" alt="智创未来" class="login-logo" />
<h2>乐绘世界创想活动乐园</h2> <h2>智创未来</h2>
<p class="login-subtitle">{{ tenantName ? tenantName + ' — 管理端登录' : '管理端登录' }}</p> <p class="login-subtitle">{{ tenantName ? tenantName + ' — 管理端登录' : '管理端登录' }}</p>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="public-auth-page"> <div class="public-auth-page">
<div class="auth-card"> <div class="auth-card">
<img src="@/assets/images/logo-icon.png" alt="乐绘世界" class="auth-logo" /> <img src="@/assets/images/logo-icon.png" alt="智创未来" class="auth-logo" />
<h2 class="auth-title">{{ isRegister ? "创建账号" : "登录" }}</h2> <h2 class="auth-title">{{ isRegister ? "创建账号" : "登录" }}</h2>
<p class="auth-subtitle"> <p class="auth-subtitle">
{{ isRegister ? "加入乐绘世界,开启创作之旅" : "欢迎回来,继续你的创作" }} {{ isRegister ? "加入乐绘世界,开启创作之旅" : "欢迎回来,继续你的创作" }}
@ -154,8 +154,8 @@ const isPhoneValid = computed(() => /^1[3-9]\d{9}$/.test(form.phone))
const showNicknameField = computed(() => isRegister.value) const showNicknameField = computed(() => isRegister.value)
const showPhoneField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'sms')) const showPhoneField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'sms'))
const showSmsCodeField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'sms')) const showSmsCodeField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'sms'))
const showUsernameField = computed(() => !isRegister.value && loginMethod.value === 'password') const showUsernameField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'password'))
const showPasswordField = computed(() => !isRegister.value && loginMethod.value === 'password') const showPasswordField = computed(() => isRegister.value || (!isRegister.value && loginMethod.value === 'password'))
// //
const baseRules: Record<string, Rule[]> = { const baseRules: Record<string, Rule[]> = {

View File

@ -114,7 +114,7 @@ const store = useAicreateStore()
const fromWorks = new URLSearchParams(window.location.search).get('from') === 'works' const fromWorks = new URLSearchParams(window.location.search).get('from') === 'works'
|| sessionStorage.getItem('le_from') === 'works' || sessionStorage.getItem('le_from') === 'works'
const brandName = '乐绘世界' const brandName = '智创未来'
function handleBack() { function handleBack() {
if (fromWorks) { if (fromWorks) {