diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index aaa055b..7ed3a85 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,9 +4,8 @@
-
-
-
+
+
@@ -119,15 +118,7 @@
-
-
-
-
- 1776019711439
-
-
-
- 1776019711439
+
@@ -513,7 +504,15 @@
1784802954848
-
+
+
+ 1784803190504
+
+
+
+ 1784803190504
+
+
@@ -533,7 +532,6 @@
-
@@ -558,7 +556,8 @@
-
+
+
\ No newline at end of file
diff --git a/.workbuddy/memory/2026-07-24.md b/.workbuddy/memory/2026-07-24.md
new file mode 100644
index 0000000..7680949
--- /dev/null
+++ b/.workbuddy/memory/2026-07-24.md
@@ -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`。
diff --git a/src/api/app/appProduct.ts b/src/api/app/appProduct.ts
index 746c63b..084dcc1 100644
--- a/src/api/app/appProduct.ts
+++ b/src/api/app/appProduct.ts
@@ -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(`${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(`${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(`${BASE}/approve/${id}`, {}, { baseUrl: WebsopyBaseUrl })
diff --git a/src/pages/developer/apps/index.tsx b/src/pages/developer/apps/index.tsx
index d7b1664..918bad3 100644
--- a/src/pages/developer/apps/index.tsx
+++ b/src/pages/developer/apps/index.tsx
@@ -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 = {
+ 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([])
const [showModal, setShowModal] = useState(false)
- const [form, setForm] = useState({ productName: '', productCode: '', appType: 20, description: '' })
+ const [form, setForm] = useState(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 (
-
+
{loading ? (
) : list.length === 0 ? (
-
- 还没有应用,点击右上角创建
+
+ 📦
+ 还没有应用
+ 点击右上角「创建」开始
) : (
-
- {list.map((app) => (
- {
- if (app.adminUrl) Taro.setClipboardData({ data: app.adminUrl })
- }}
- />
- ))}
+
+ {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 (
+
+ {/* 卡片头部:图标 + 名称 + 类型 */}
+
+ {/* Logo / Emoji */}
+
+ {app.logo ? (
+
+ ) : (
+ {emoji}
+ )}
+
+ {/* 名称 + 标识 */}
+
+
+ {app.productName || '未命名应用'}
+
+
+ {APP_TYPE_NAME[app.appType as number] || '未知类型'} · {app.productCode || ''}
+
+
+
+
+ {/* 状态标签 */}
+
+
+ {pubName}
+
+
+ {runName}
+
+ {app.createTime && (
+
+ {app.createTime.slice(0, 10)}
+
+ )}
+
+
+ {/* 描述 */}
+ {app.description && (
+
+
+ {app.description}
+
+
+ )}
+
+ {/* 底部操作区 */}
+
+ copyLink(app.adminUrl, '管理后台')}
+ >
+ 管理后台
+
+
+ copyLink(app.homeUrl, '首页')}
+ >
+ 首页
+
+
+ openEdit(app)}
+ >
+ 编辑
+
+
+ handleDelete(app)}
+ >
+ 删除
+
+
+
+ )
+ })}
)}
{showModal && (
-
-
- 创建应用
+ setShowModal(false)}
+ >
+ e.stopPropagation()}
+ style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
+ >
+
+ {editing ? '编辑应用' : '创建应用'}
+ setShowModal(false)}>×
+
+
应用名称
{
onInput={(e) => setForm({ ...form, productName: e.detail.value })}
/>
+
- 应用标识
+
+ 应用标识{editing && '(不可修改)'}
+
setForm({ ...form, productCode: e.detail.value })}
/>
+
应用类型
@@ -132,6 +344,7 @@ const MyAppsPage: React.FC = () => {
))}
+
描述
+
- setShowModal(false)}>
+ setShowModal(false)}
+ >
取消
-
- {submitting ? '提交中...' : '创建'}
+
+
+ {submitting ? '提交中...' : editing ? '保存' : '创建'}
+