feat(user): 合并切换开发者菜单并优化首页轮播图显示

- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心
- 已审核用户菜单高亮显示“进入开发者中心”
- 去除冗余开发者中心菜单条目,简化结构
- 重新设计开发者中心页面UI,统一门店中心风格
- 顶部展示开发者头像及昵称手机号信息
- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标
- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片
- 未登录或非开发者状态显示相应空态及操作按钮
- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验
- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示
This commit is contained in:
2026-07-24 14:19:25 +08:00
parent 0713ad4cf7
commit efcbaa5b60
4 changed files with 296 additions and 48 deletions

29
.idea/workspace.xml generated
View File

@@ -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&#10;&#10;- 用户菜单根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示">
<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): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限示">
<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): 优化隐私协议同意逻辑及样式&#10;&#10;- 修正api-keys样式导入路径改为developer路径&#10;- 在开发者申请页面增加服务协议和隐私政策勾选框&#10;- 提交时校验协议是否已勾选,未勾选时提示用户&#10;- 优化申请页面协议区域样式及交互&#10;- 在用户验证页面添加隐私政策同意勾选框&#10;- 提交用户验证表单时检查协议同意状态&#10;- 增加打开服务协议和隐私政策的链接跳转&#10;- 更新项目配置增加passport/webview路由配置&#10;- 修复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): 移除开发者中心和企业控制台的自定义导航样式&#10;&#10;- 删除了开发者中心页面配置中的 navigationStyle: 'custom'&#10;- 删除了企业控制台页面配置中的 navigationStyle: 'custom'&#10;- 简化了页面配置,保持默认导航样式&#10;- 有助于提升页面兼容性和维护性">
<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): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
<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): 优化订阅号获取逻辑并调整支付组件样式&#10;&#10;- 优先从小程序码 scene 参数读取 subscriptionNo提升参数兼容性&#10;- 对 scene 参数进行解码,避免编码引起的问题&#10;- 移除支付组件价格显示处的固定颜色设置,增强样式灵活性" />
<MESSAGE value="fix(utils): 优化小程序端复制文本功能体验&#10;&#10;- 小程序端直接调用原生 wx.setClipboardData避免 Taro Promise 封装问题&#10;- 避免微信小程序重复显示复制成功提示和误报复制失败&#10;- 失败时仅在小程序端输出错误日志,非小程序端显示提示&#10;- 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验&#10;- 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求" />
<MESSAGE value="fix(index): 优化外链提示及删除复制咨询模板功能&#10;&#10;- 替换小程序内外链打开方式提示为弹窗提醒用户手动复制&#10;- 删除复制咨询模板功能及相关按钮&#10;- 修改联系我们文案,突出直接电话沟通或需求反馈&#10;- 移除无用导入的复制文本工具函数copyText" />
<MESSAGE value="feat(utils): 优化请求函数URL拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中url未处理的问题" />
@@ -558,7 +556,8 @@
<MESSAGE value="feat(user): 合并并切换“申请开发者”菜单项状态&#10;&#10;- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”&#10;- 已通过审核用户直接显示“进入开发者中心”且高亮显示&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 调整平台管理页和门店中心入口权限为仅超级管理员可访问&#10;- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白&#10;- 更新相关文档,完善审核通过后店员记录添加流程" />
<MESSAGE value="fix(auth): 统一超级管理员标识为布尔值true&#10;&#10;- 将所有页面和菜单中超级管理员校验由数字1改为布尔true&#10;- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示&#10;- 门店中心页面移除店员门槛,改为超级管理员访问限制&#10;- 非超级管理员访问门店中心弹窗提示并返回&#10;- 首页工作台入口及相关访问权限判定切换为布尔判断&#10;- 优化首屏加载状态,避免权限校验闪烁和误判" />
<MESSAGE value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI统一为门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
<MESSAGE value="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限示" />
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
</component>
</project>

View 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`

View File

@@ -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 })

View File

@@ -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>