feat(add): 新增多页面新增和编辑表单功能
- 添加编辑和新增收货地址页面,支持表单数据加载和提交 - 新增应用密钥凭证、新增应用操作动态、新增应用成员、新增应用版本页面配置 - 实现文章新增及编辑页面,包含图片上传及多种文章属性配置 - 增加注册会员页面,支持头像上传、手机号获取和邀请人关系处理 - 引入统一表单提交成功和失败处理,支持编辑模式数据回显 - 配置统一eslint和editorconfig规则,增强代码规范和编辑体验 - 新增.gitignore规则,屏蔽无关文件和目录,优化版本管理
This commit is contained in:
3
src/user/apps/index.config.ts
Normal file
3
src/user/apps/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default defineAppConfig({
|
||||
navigationBarTitleText: '我的应用',
|
||||
})
|
||||
192
src/user/apps/index.tsx
Normal file
192
src/user/apps/index.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { Empty, InfiniteLoading, PullToRefresh, Tag } from '@nutui/nutui-react-taro'
|
||||
import { useThemeStyles } from '@/hooks/useTheme'
|
||||
import { listJoinedApps } from '@/api/app/appProduct'
|
||||
import type { AppProduct } from '@/api/app/appProduct/model'
|
||||
import { APP_TYPE_NAME, STATUS_NAME, STATUS_COLOR } from '@/api/app/appProduct/model'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const MyAppsPage = () => {
|
||||
const themeStyles = useThemeStyles()
|
||||
const [list, setList] = useState<AppProduct[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [current, setCurrent] = useState(1)
|
||||
|
||||
const loadApps = useCallback(async (isRefresh = false) => {
|
||||
if (loading) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const page = isRefresh ? 1 : current
|
||||
const res = await listJoinedApps({ page, limit: 20 })
|
||||
const newList = res?.list || []
|
||||
if (isRefresh) {
|
||||
setList(newList)
|
||||
setCurrent(2)
|
||||
} else {
|
||||
setList(prev => [...prev, ...newList])
|
||||
setCurrent(prev => prev + 1)
|
||||
}
|
||||
setHasMore(newList.length >= 20)
|
||||
} catch (e) {
|
||||
console.error('加载应用失败', e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [loading, current])
|
||||
|
||||
useDidShow(() => {
|
||||
loadApps(true)
|
||||
})
|
||||
|
||||
const handleEnterAdmin = (app: AppProduct) => {
|
||||
if (!app.adminUrl) {
|
||||
Taro.showToast({ title: '暂无管理入口', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const url = app.adminUrl.startsWith('http') ? app.adminUrl : `https://${app.adminUrl}`
|
||||
Taro.navigateTo({ url: `/passport/webview/index?url=${encodeURIComponent(url)}` })
|
||||
}
|
||||
|
||||
const handleEnterHome = (app: AppProduct) => {
|
||||
if (!app.homeUrl && !app.domain) {
|
||||
Taro.showToast({ title: '暂无访问地址', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const url = (app.homeUrl || app.domain || '').startsWith('http')
|
||||
? (app.homeUrl || app.domain)
|
||||
: `https://${app.homeUrl || app.domain}`
|
||||
Taro.navigateTo({ url: `/passport/webview/index?url=${encodeURIComponent(url)}` })
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="bg-gray-100 min-h-screen">
|
||||
{/* 头部 */}
|
||||
<View className="px-4 py-6" style={themeStyles.primaryBackground}>
|
||||
<Text className="text-white text-lg font-bold">我的应用</Text>
|
||||
<View className="mt-1">
|
||||
<Text className="text-white text-sm opacity-80">管理您开通的应用与服务</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<PullToRefresh onRefresh={() => loadApps(true)} className="mt-4">
|
||||
<View className="mx-4 rounded-xl overflow-hidden">
|
||||
{list.length === 0 && !loading ? (
|
||||
<View className="bg-white rounded-xl">
|
||||
<Empty description="暂无应用,请前往PC端开通" imageSize={80} className="py-16" />
|
||||
<View className="px-4 pb-6">
|
||||
<View
|
||||
className="text-center text-sm text-blue-500"
|
||||
onClick={() => Taro.navigateTo({
|
||||
url: '/passport/webview/index?url=' + encodeURIComponent('https://websopy.websoft.top/products')
|
||||
})}
|
||||
>
|
||||
去开通应用 >
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
list.map((app) => {
|
||||
const statusName = STATUS_NAME[app.status ?? 0] || '未知'
|
||||
const statusColor = STATUS_COLOR[app.status ?? 0] || '#6b7280'
|
||||
const typeName = APP_TYPE_NAME[app.appType ?? 10] || '应用'
|
||||
const isExpired = app.expirationTime && dayjs(app.expirationTime).isBefore(dayjs())
|
||||
|
||||
return (
|
||||
<View
|
||||
key={app.productId}
|
||||
className="bg-white mx-0 mb-3 rounded-xl p-4"
|
||||
>
|
||||
<View className="flex items-start gap-3">
|
||||
{/* 图标 */}
|
||||
<View className="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||
{app.icon ? (
|
||||
<image src={app.icon} className="w-full h-full" mode="aspectFit" />
|
||||
) : (
|
||||
<Text className="text-lg text-gray-400">{typeName[0]}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 信息 */}
|
||||
<View className="flex-1 min-w-0">
|
||||
<View className="flex items-center gap-2 mb-1">
|
||||
<Text className="font-semibold text-gray-900 text-sm truncate flex-1">
|
||||
{app.productName || '未命名应用'}
|
||||
</Text>
|
||||
<Tag
|
||||
plain
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
padding: '0 4px',
|
||||
borderColor: statusColor,
|
||||
color: statusColor,
|
||||
}}
|
||||
>
|
||||
{isExpired ? '已过期' : statusName}
|
||||
</Tag>
|
||||
</View>
|
||||
|
||||
<View className="flex items-center gap-2 mb-1">
|
||||
<Text className="text-xs text-gray-400">{typeName}</Text>
|
||||
{app.version && (
|
||||
<Text className="text-xs text-gray-300">v{app.version}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{app.description && (
|
||||
<Text className="text-xs text-gray-500 line-clamp-2 mb-2">{app.description}</Text>
|
||||
)}
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<View className="flex gap-2 mt-2">
|
||||
{app.adminUrl && (
|
||||
<View
|
||||
className="px-3 py-2 bg-blue-500 rounded-lg text-center"
|
||||
onClick={() => handleEnterAdmin(app)}
|
||||
>
|
||||
<Text className="text-xs text-blue-600">管理后台</Text>
|
||||
</View>
|
||||
)}
|
||||
{(app.homeUrl || app.domain) && (
|
||||
<View
|
||||
className="px-3 py-2 bg-green-50 rounded-lg text-center"
|
||||
onClick={() => handleEnterHome(app)}
|
||||
>
|
||||
<Text className="text-xs text-green-600">访问前台</Text>
|
||||
</View>
|
||||
)}
|
||||
{app.expirationTime && (
|
||||
<View className="flex items-center ml-auto">
|
||||
<Text className={`text-xs ${isExpired ? 'text-red-400' : 'text-gray-300'}`}>
|
||||
到期:{dayjs(app.expirationTime).format('YYYY-MM-DD')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</View>
|
||||
|
||||
<InfiniteLoading
|
||||
hasMore={hasMore}
|
||||
onLoadMore={() => loadApps(false)}
|
||||
loading={loading}
|
||||
>
|
||||
<View className="py-4 text-center">
|
||||
<Text className="text-xs text-gray-400">
|
||||
{loading ? '加载中...' : hasMore ? '下拉加载更多' : '没有更多了'}
|
||||
</Text>
|
||||
</View>
|
||||
</InfiniteLoading>
|
||||
</PullToRefresh>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyAppsPage
|
||||
Reference in New Issue
Block a user