feat(user): 合并切换开发者菜单并优化首页轮播图显示
- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示
This commit is contained in:
29
.idea/workspace.xml
generated
29
.idea/workspace.xml
generated
@@ -4,9 +4,8 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI - 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心” - 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 重新设计开发者中心页面UI,统一为门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态提示及操作按钮 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示">
|
||||
<change beforePath="$PROJECT_DIR$/.workbuddy/memory/2026-07-23.md" beforeDir="false" afterPath="$PROJECT_DIR$/.workbuddy/memory/2026-07-23.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/pages/index/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/index/index.tsx" afterDir="false" />
|
||||
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -119,15 +118,7 @@
|
||||
<workItem from="1784133736973" duration="4614000" />
|
||||
<workItem from="1784585070286" duration="3047000" />
|
||||
<workItem from="1784638429525" duration="16147000" />
|
||||
<workItem from="1784798575885" duration="3747000" />
|
||||
</task>
|
||||
<task id="LOCAL-00025" summary="feat(api-keys): 优化隐私协议同意逻辑及样式 - 修正api-keys样式导入路径,改为developer路径 - 在开发者申请页面增加服务协议和隐私政策勾选框 - 提交时校验协议是否已勾选,未勾选时提示用户 - 优化申请页面协议区域样式及交互 - 在用户验证页面添加隐私政策同意勾选框 - 提交用户验证表单时检查协议同意状态 - 增加打开服务协议和隐私政策的链接跳转 - 更新项目配置,增加passport/webview路由配置 - 修复createTicket函数中日期格式正则表达式问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1776019711439</created>
|
||||
<option name="number" value="00025" />
|
||||
<option name="presentableId" value="LOCAL-00025" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1776019711439</updated>
|
||||
<workItem from="1784798575885" duration="4896000" />
|
||||
</task>
|
||||
<task id="LOCAL-00026" summary="refactor(config): 移除开发者中心和企业控制台的自定义导航样式 - 删除了开发者中心页面配置中的 navigationStyle: 'custom' - 删除了企业控制台页面配置中的 navigationStyle: 'custom' - 简化了页面配置,保持默认导航样式 - 有助于提升页面兼容性和维护性">
|
||||
<option name="closed" value="true" />
|
||||
@@ -513,7 +504,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784802954848</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="74" />
|
||||
<task id="LOCAL-00074" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784803190504</created>
|
||||
<option name="number" value="00074" />
|
||||
<option name="presentableId" value="LOCAL-00074" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784803190504</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="75" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -533,7 +532,6 @@
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<MESSAGE value="fix(pay): 优化订阅号获取逻辑并调整支付组件样式 - 优先从小程序码 scene 参数读取 subscriptionNo,提升参数兼容性 - 对 scene 参数进行解码,避免编码引起的问题 - 移除支付组件价格显示处的固定颜色设置,增强样式灵活性" />
|
||||
<MESSAGE value="fix(utils): 优化小程序端复制文本功能体验 - 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题 - 避免微信小程序重复显示复制成功提示和误报复制失败 - 失败时仅在小程序端输出错误日志,非小程序端显示提示 - 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验 - 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求" />
|
||||
<MESSAGE value="fix(index): 优化外链提示及删除复制咨询模板功能 - 替换小程序内外链打开方式提示为弹窗提醒用户手动复制 - 删除复制咨询模板功能及相关按钮 - 修改联系我们文案,突出直接电话沟通或需求反馈 - 移除无用导入的复制文本工具函数copyText" />
|
||||
<MESSAGE value="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题" />
|
||||
@@ -558,7 +556,8 @@
|
||||
<MESSAGE value="feat(user): 合并并切换“申请开发者”菜单项状态 - 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心” - 已通过审核用户直接显示“进入开发者中心”且高亮显示 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 调整平台管理页和门店中心入口权限为仅超级管理员可访问 - 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白 - 更新相关文档,完善审核通过后店员记录添加流程" />
|
||||
<MESSAGE value="fix(auth): 统一超级管理员标识为布尔值true - 将所有页面和菜单中超级管理员校验由数字1改为布尔true - 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示 - 门店中心页面移除店员门槛,改为超级管理员访问限制 - 非超级管理员访问门店中心弹窗提示并返回 - 首页工作台入口及相关访问权限判定切换为布尔判断 - 优化首屏加载状态,避免权限校验闪烁和误判" />
|
||||
<MESSAGE value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI - 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心” - 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 重新设计开发者中心页面UI,统一为门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态提示及操作按钮 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI - 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心” - 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 重新设计开发者中心页面UI,统一为门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态提示及操作按钮 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
|
||||
<MESSAGE value="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
|
||||
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
|
||||
</component>
|
||||
</project>
|
||||
12
.workbuddy/memory/2026-07-24.md
Normal file
12
.workbuddy/memory/2026-07-24.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 2026-07-24 工作日志
|
||||
|
||||
## 应用列表页改版(pages/developer/apps/index)
|
||||
|
||||
将「我的应用」列表从 CellRow 纯文本行改为卡片式布局:
|
||||
- **卡片结构**:左侧 Logo/Emoji 图标 + 应用名 + 类型标识;中部双状态标签(发布状态 + 运行状态,带颜色);底部两行截断描述;底部操作栏(管理后台 / 首页 / 编辑 / 删除)。
|
||||
- **Logo**:`AppProduct.logo` 有值时用 `Image` 展示,否则按 `appType` 显示 emoji 占位(🌐💚🤖🍎⚙️ 等)。
|
||||
- **操作行为**:管理后台/首页按钮 → `setClipboardData` 复制链接并 toast;编辑 → 弹窗预填数据调 `updateAppProduct`;删除 → `Taro.showModal` 确认后调 `deleteAppProduct`。
|
||||
- **编辑模式**:复用创建弹窗,`productCode` 编辑时 disabled,按钮文案区分「创建」/「保存」。
|
||||
- **API 新增**:`appProduct.ts` 加了 `updateAppProduct`(PUT /app/product/update)和 `deleteAppProduct`(DELETE /app/product/delete/{id}),引入 `put`/`del` from request utils。
|
||||
- **空状态**:带 emoji 图标 + 引导文案。
|
||||
- 后端 Controller 在 websopy-java(不是 com.gxwebsoft.core),路径前缀 `/api/app/product`。
|
||||
@@ -1,5 +1,5 @@
|
||||
// 应用产品(对齐 PC /developer/apps 与 /admin 应用管理,基址走 websopy-api)
|
||||
import { get, post } from '@/utils/request'
|
||||
import { get, post, put, del } from '@/utils/request'
|
||||
import { WebsopyBaseUrl } from '@/config/app'
|
||||
|
||||
const BASE = '/app/product'
|
||||
@@ -57,6 +57,20 @@ export async function pageAllApps(params: any) {
|
||||
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||
}
|
||||
|
||||
/** 更新应用 */
|
||||
export async function updateAppProduct(data: any) {
|
||||
const res = await put<any>(`${BASE}/update`, data, { baseUrl: WebsopyBaseUrl })
|
||||
if (res?.code === 0) return res.data
|
||||
return Promise.reject(new Error(res?.message || '更新失败'))
|
||||
}
|
||||
|
||||
/** 删除应用 */
|
||||
export async function deleteAppProduct(id: number) {
|
||||
const res = await del<any>(`${BASE}/delete/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||
if (res?.code === 0) return res.data
|
||||
return Promise.reject(new Error(res?.message || '删除失败'))
|
||||
}
|
||||
|
||||
/** 审核通过 */
|
||||
export async function approvePublishReview(id: number) {
|
||||
const res = await post<any>(`${BASE}/approve/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { View, Text, ScrollView, Input, Textarea } from '@tarojs/components'
|
||||
import { View, Text, ScrollView, Input, Textarea, Image } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import NavBar from '@/components/NavBar'
|
||||
import CellRow from '@/components/common/CellRow'
|
||||
import Loading from '@/components/common/Loading'
|
||||
import { useUser } from '@/hooks/useUser'
|
||||
import { getMyApps, addAppProduct } from '@/api/app/appProduct'
|
||||
import { getPageList, APP_TYPE_NAME } from '@/utils/devcenter'
|
||||
import { getMyApps, addAppProduct, updateAppProduct, deleteAppProduct } from '@/api/app/appProduct'
|
||||
import {
|
||||
getPageList,
|
||||
APP_TYPE_NAME,
|
||||
PUBLISH_STATUS_NAME,
|
||||
STATUS_NAME,
|
||||
publishStatusColor,
|
||||
} from '@/utils/devcenter'
|
||||
|
||||
definePageConfig({ navigationBarTitleText: '我的应用' })
|
||||
|
||||
@@ -18,20 +23,62 @@ const APP_TYPE_OPTIONS = [
|
||||
{ type: 110, name: '平台应用' },
|
||||
]
|
||||
|
||||
/** 应用类型 emoji 映射(无 logo 时用) */
|
||||
const APP_TYPE_EMOJI: Record<number, string> = {
|
||||
10: '🌐',
|
||||
20: '💚',
|
||||
30: '🎵',
|
||||
40: '🔍',
|
||||
50: '💙',
|
||||
60: '🤖',
|
||||
70: '🍎',
|
||||
80: '💻',
|
||||
90: '🖥️',
|
||||
100: '🔌',
|
||||
110: '⚙️',
|
||||
}
|
||||
|
||||
/** 运行状态颜色 */
|
||||
function statusColor(status?: number): string {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '#16a34a'
|
||||
case 2:
|
||||
case 4:
|
||||
return '#d97706'
|
||||
case 3:
|
||||
case 5:
|
||||
return '#dc2626'
|
||||
default:
|
||||
return '#6b7280'
|
||||
}
|
||||
}
|
||||
|
||||
interface AppForm {
|
||||
productId?: number
|
||||
productName: string
|
||||
productCode: string
|
||||
appType: number
|
||||
description: string
|
||||
}
|
||||
|
||||
const EMPTY_FORM: AppForm = { productName: '', productCode: '', appType: 20, description: '' }
|
||||
|
||||
const MyAppsPage: React.FC = () => {
|
||||
const { isLoggedIn } = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [list, setList] = useState<any[]>([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [form, setForm] = useState({ productName: '', productCode: '', appType: 20, description: '' })
|
||||
const [form, setForm] = useState<AppForm>(EMPTY_FORM)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [editing, setEditing] = useState(false)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await getMyApps({ page: 1, limit: 20 })
|
||||
setList(getPageList(data))
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false)
|
||||
@@ -43,7 +90,20 @@ const MyAppsPage: React.FC = () => {
|
||||
}, [isLoggedIn, load])
|
||||
|
||||
const openCreate = () => {
|
||||
setForm({ productName: '', productCode: '', appType: 20, description: '' })
|
||||
setForm(EMPTY_FORM)
|
||||
setEditing(false)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
const openEdit = (app: any) => {
|
||||
setForm({
|
||||
productId: app.productId,
|
||||
productName: app.productName || '',
|
||||
productCode: app.productCode || '',
|
||||
appType: app.appType || 20,
|
||||
description: app.description || '',
|
||||
})
|
||||
setEditing(true)
|
||||
setShowModal(true)
|
||||
}
|
||||
|
||||
@@ -52,53 +112,199 @@ const MyAppsPage: React.FC = () => {
|
||||
Taro.showToast({ title: '请填写应用名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!/^[a-z][a-z0-9]*$/.test(form.productCode)) {
|
||||
if (!editing && !/^[a-z][a-z0-9]*$/.test(form.productCode)) {
|
||||
Taro.showToast({ title: '标识须为小写字母/数字', icon: 'none' })
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await addAppProduct(form)
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' })
|
||||
if (editing) {
|
||||
await updateAppProduct(form)
|
||||
Taro.showToast({ title: '更新成功', icon: 'success' })
|
||||
} else {
|
||||
await addAppProduct(form)
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' })
|
||||
}
|
||||
setShowModal(false)
|
||||
load()
|
||||
} catch (e: any) {
|
||||
Taro.showToast({ title: e?.message || '创建失败', icon: 'none' })
|
||||
Taro.showToast({ title: e?.message || '操作失败', icon: 'none' })
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (app: any) => {
|
||||
Taro.showModal({
|
||||
title: '删除应用',
|
||||
content: `确定删除「${app.productName || '未命名应用'}」吗?此操作不可恢复。`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#dc2626',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
Taro.showLoading({ title: '删除中...' })
|
||||
try {
|
||||
await deleteAppProduct(app.productId)
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({ title: '删除成功', icon: 'success' })
|
||||
load()
|
||||
} catch (e: any) {
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({ title: e?.message || '删除失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const copyLink = (url: string, label: string) => {
|
||||
if (!url) {
|
||||
Taro.showToast({ title: `${label}未配置`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
Taro.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
Taro.showToast({ title: `${label}链接已复制`, icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-100'>
|
||||
<NavBar title='我的应用' rightText='创建' onRightClick={openCreate} />
|
||||
<ScrollView scrollY>
|
||||
<ScrollView scrollY className='pb-4'>
|
||||
{loading ? (
|
||||
<Loading />
|
||||
) : list.length === 0 ? (
|
||||
<View className='flex flex-col items-center justify-center py-24'>
|
||||
<Text className='text-gray-400 text-sm'>还没有应用,点击右上角创建</Text>
|
||||
<View className='flex flex-col items-center justify-center py-32'>
|
||||
<Text className='text-5xl mb-4'>📦</Text>
|
||||
<Text className='text-gray-400 text-sm mb-1'>还没有应用</Text>
|
||||
<Text className='text-gray-300 text-xs'>点击右上角「创建」开始</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='mt-3'>
|
||||
{list.map((app) => (
|
||||
<CellRow
|
||||
key={app.productId}
|
||||
title={app.productName || '未命名应用'}
|
||||
subtitle={`${APP_TYPE_NAME[app.appType as number] || '未知类型'} · ${app.productCode || ''}`}
|
||||
onClick={() => {
|
||||
if (app.adminUrl) Taro.setClipboardData({ data: app.adminUrl })
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<View className='px-3 pt-3'>
|
||||
{list.map((app) => {
|
||||
const pubColor = publishStatusColor(app.publishStatus)
|
||||
const runColor = statusColor(app.status)
|
||||
const emoji = APP_TYPE_EMOJI[app.appType as number] || '📦'
|
||||
const pubName = PUBLISH_STATUS_NAME[app.publishStatus as string] || '开发中'
|
||||
const runName = STATUS_NAME[app.status as number] || '未开通'
|
||||
return (
|
||||
<View
|
||||
key={app.productId}
|
||||
className='bg-white rounded-xl mb-3 overflow-hidden'
|
||||
style={{ boxShadow: '0 1px 4px rgba(0,0,0,0.06)' }}
|
||||
>
|
||||
{/* 卡片头部:图标 + 名称 + 类型 */}
|
||||
<View className='flex items-start p-4'>
|
||||
{/* Logo / Emoji */}
|
||||
<View
|
||||
className='w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 mr-3'
|
||||
style={{ background: '#f3f4f6' }}
|
||||
>
|
||||
{app.logo ? (
|
||||
<Image
|
||||
src={app.logo}
|
||||
className='w-12 h-12 rounded-xl'
|
||||
mode='aspectFill'
|
||||
/>
|
||||
) : (
|
||||
<Text className='text-2xl'>{emoji}</Text>
|
||||
)}
|
||||
</View>
|
||||
{/* 名称 + 标识 */}
|
||||
<View className='flex-1 min-w-0'>
|
||||
<Text className='text-base font-medium text-gray-800 block truncate'>
|
||||
{app.productName || '未命名应用'}
|
||||
</Text>
|
||||
<Text className='text-xs text-gray-400 mt-0.5 block'>
|
||||
{APP_TYPE_NAME[app.appType as number] || '未知类型'} · {app.productCode || ''}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 状态标签 */}
|
||||
<View className='flex items-center gap-2 px-4 mb-3'>
|
||||
<Text
|
||||
className='text-xs px-2 py-0.5 rounded-full'
|
||||
style={{ color: pubColor, background: pubColor + '1a' }}
|
||||
>
|
||||
{pubName}
|
||||
</Text>
|
||||
<Text
|
||||
className='text-xs px-2 py-0.5 rounded-full'
|
||||
style={{ color: runColor, background: runColor + '1a' }}
|
||||
>
|
||||
{runName}
|
||||
</Text>
|
||||
{app.createTime && (
|
||||
<Text className='text-xs text-gray-300 ml-auto'>
|
||||
{app.createTime.slice(0, 10)}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 描述 */}
|
||||
{app.description && (
|
||||
<View className='px-4 pb-3'>
|
||||
<Text className='text-xs text-gray-500 leading-relaxed' style={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
|
||||
{app.description}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 底部操作区 */}
|
||||
<View className='flex items-center border-t border-gray-50'>
|
||||
<View
|
||||
className='flex-1 flex items-center justify-center py-2.5'
|
||||
onClick={() => copyLink(app.adminUrl, '管理后台')}
|
||||
>
|
||||
<Text className='text-xs text-gray-500'>管理后台</Text>
|
||||
</View>
|
||||
<View className='w-px h-4 bg-gray-100' />
|
||||
<View
|
||||
className='flex-1 flex items-center justify-center py-2.5'
|
||||
onClick={() => copyLink(app.homeUrl, '首页')}
|
||||
>
|
||||
<Text className='text-xs text-gray-500'>首页</Text>
|
||||
</View>
|
||||
<View className='w-px h-4 bg-gray-100' />
|
||||
<View
|
||||
className='flex-1 flex items-center justify-center py-2.5'
|
||||
onClick={() => openEdit(app)}
|
||||
>
|
||||
<Text className='text-xs text-blue-500'>编辑</Text>
|
||||
</View>
|
||||
<View className='w-px h-4 bg-gray-100' />
|
||||
<View
|
||||
className='flex-1 flex items-center justify-center py-2.5'
|
||||
onClick={() => handleDelete(app)}
|
||||
>
|
||||
<Text className='text-xs text-red-500'>删除</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{showModal && (
|
||||
<View style={{ position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.4)', zIndex: 999 }}>
|
||||
<View className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4'>
|
||||
<Text className='text-base font-medium block mb-3'>创建应用</Text>
|
||||
<View
|
||||
style={{ position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.4)', zIndex: 999 }}
|
||||
onClick={() => setShowModal(false)}
|
||||
>
|
||||
<View
|
||||
className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
|
||||
>
|
||||
<View className='flex items-center justify-between mb-4'>
|
||||
<Text className='text-base font-medium'>{editing ? '编辑应用' : '创建应用'}</Text>
|
||||
<Text className='text-gray-300 text-lg' onClick={() => setShowModal(false)}>×</Text>
|
||||
</View>
|
||||
|
||||
<View className='mb-3'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>应用名称</Text>
|
||||
<Input
|
||||
@@ -108,15 +314,21 @@ const MyAppsPage: React.FC = () => {
|
||||
onInput={(e) => setForm({ ...form, productName: e.detail.value })}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='mb-3'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>应用标识</Text>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>
|
||||
应用标识{editing && '(不可修改)'}
|
||||
</Text>
|
||||
<Input
|
||||
className='bg-gray-100 rounded-lg px-3 py-2 text-sm'
|
||||
placeholder='如:weishop'
|
||||
value={form.productCode}
|
||||
disabled={editing}
|
||||
style={editing ? { opacity: 0.5 } : {}}
|
||||
onInput={(e) => setForm({ ...form, productCode: e.detail.value })}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='mb-3'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>应用类型</Text>
|
||||
<View className='flex flex-wrap gap-2'>
|
||||
@@ -132,6 +344,7 @@ const MyAppsPage: React.FC = () => {
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='mb-4'>
|
||||
<Text className='text-xs text-gray-500 block mb-1'>描述</Text>
|
||||
<Textarea
|
||||
@@ -142,12 +355,22 @@ const MyAppsPage: React.FC = () => {
|
||||
style={{ height: '80px' }}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='flex gap-3'>
|
||||
<View className='flex-1 py-2.5 rounded-lg bg-gray-100 text-center' onClick={() => setShowModal(false)}>
|
||||
<View
|
||||
className='flex-1 py-2.5 rounded-lg bg-gray-100 text-center'
|
||||
onClick={() => setShowModal(false)}
|
||||
>
|
||||
<Text className='text-sm text-gray-600'>取消</Text>
|
||||
</View>
|
||||
<View className='flex-1 py-2.5 rounded-lg text-center' style={{ background: '#2563eb' }} onClick={submit}>
|
||||
<Text className='text-sm text-white'>{submitting ? '提交中...' : '创建'}</Text>
|
||||
<View
|
||||
className='flex-1 py-2.5 rounded-lg text-center'
|
||||
style={{ background: '#2563eb' }}
|
||||
onClick={submit}
|
||||
>
|
||||
<Text className='text-sm text-white'>
|
||||
{submitting ? '提交中...' : editing ? '保存' : '创建'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user