feat(shop): 新增文章管理功能
- 添加文章列表页面,支持搜索、分页加载、下拉刷新等功能 - 实现文章详情查看、编辑和删除功能- 新增文章发布页面,支持富文本编辑和图片上传 - 优化地址管理页面,统一底部按钮样式 - 新增 FixedButton 组件用于底部固定按钮
This commit is contained in:
@@ -61,7 +61,8 @@ export default defineAppConfig({
|
|||||||
'orderConfirm/index',
|
'orderConfirm/index',
|
||||||
'orderConfirmCart/index',
|
'orderConfirmCart/index',
|
||||||
'search/index',
|
'search/index',
|
||||||
'shopArticle/index'
|
'shopArticle/index',
|
||||||
|
'shopArticle/add'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
26
src/components/FixedButton.tsx
Normal file
26
src/components/FixedButton.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {View} from '@tarojs/components';
|
||||||
|
import {Button} from '@nutui/nutui-react-taro'
|
||||||
|
|
||||||
|
function FixedButton({text, onClick, icon}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 底部安全区域占位 */
|
||||||
|
}
|
||||||
|
<View className="h-20 w-full"></View>
|
||||||
|
<View
|
||||||
|
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
|
||||||
|
<Button
|
||||||
|
type="success"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
icon={icon}
|
||||||
|
className="px-6"
|
||||||
|
onClick={onClick}>
|
||||||
|
{text || '新增'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FixedButton;
|
||||||
4
src/shop/shopArticle/add.config.ts
Normal file
4
src/shop/shopArticle/add.config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '新增收货地址',
|
||||||
|
navigationBarTextStyle: 'black'
|
||||||
|
})
|
||||||
323
src/shop/shopArticle/add.tsx
Normal file
323
src/shop/shopArticle/add.tsx
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
import {useEffect, useState, useRef} from "react";
|
||||||
|
import {useRouter} from '@tarojs/taro'
|
||||||
|
import {Button, Loading, CellGroup, Input, TextArea, Form, Switch, InputNumber, Radio, Image} from '@nutui/nutui-react-taro'
|
||||||
|
import {Edit, Upload as UploadIcon} from '@nutui/icons-react-taro'
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import {View} from '@tarojs/components'
|
||||||
|
import {ShopArticle} from "@/api/shop/shopArticle/model";
|
||||||
|
import {getShopArticle, addShopArticle, updateShopArticle} from "@/api/shop/shopArticle";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
|
||||||
|
const AddShopArticle = () => {
|
||||||
|
const {params} = useRouter();
|
||||||
|
const [loading, setLoading] = useState<boolean>(true)
|
||||||
|
const [formData, setFormData] = useState<ShopArticle>({
|
||||||
|
type: 0, // 默认常规文章
|
||||||
|
status: 0, // 默认已发布
|
||||||
|
permission: 0, // 默认所有人可见
|
||||||
|
recommend: 0, // 默认不推荐
|
||||||
|
showType: 10, // 默认小图展示
|
||||||
|
virtualViews: 0, // 默认虚拟阅读量
|
||||||
|
actualViews: 0, // 默认实际阅读量
|
||||||
|
sortNumber: 0 // 默认排序
|
||||||
|
})
|
||||||
|
const formRef = useRef<any>(null)
|
||||||
|
|
||||||
|
// 判断是编辑还是新增模式
|
||||||
|
const isEditMode = !!params.id
|
||||||
|
const articleId = params.id ? Number(params.id) : undefined
|
||||||
|
|
||||||
|
// 文章类型选项
|
||||||
|
const typeOptions = [
|
||||||
|
{ text: '常规文章', value: 0 },
|
||||||
|
{ text: '视频文章', value: 1 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 状态选项
|
||||||
|
const statusOptions = [
|
||||||
|
{ text: '已发布', value: 0 },
|
||||||
|
{ text: '待审核', value: 1 },
|
||||||
|
{ text: '已驳回', value: 2 },
|
||||||
|
{ text: '违规内容', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 可见性选项
|
||||||
|
const permissionOptions = [
|
||||||
|
{ text: '所有人可见', value: 0 },
|
||||||
|
{ text: '登录可见', value: 1 },
|
||||||
|
{ text: '密码可见', value: 2 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 显示方式选项
|
||||||
|
const showTypeOptions = [
|
||||||
|
{ text: '小图展示', value: 10 },
|
||||||
|
{ text: '大图展示', value: 20 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const reload = async () => {
|
||||||
|
// 如果是编辑模式,加载文章数据
|
||||||
|
if (isEditMode && articleId) {
|
||||||
|
try {
|
||||||
|
const article = await getShopArticle(articleId)
|
||||||
|
setFormData(article)
|
||||||
|
// 更新表单值
|
||||||
|
if (formRef.current) {
|
||||||
|
formRef.current.setFieldsValue(article)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载文章失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载文章失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片上传处理
|
||||||
|
const handleImageUpload = async () => {
|
||||||
|
try {
|
||||||
|
const res = await Taro.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
|
||||||
|
// 这里应该调用上传接口,暂时使用本地路径
|
||||||
|
const imagePath = res.tempFilePaths[0];
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
image: imagePath
|
||||||
|
});
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '图片选择成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '图片选择失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitSucceed = async (values: any) => {
|
||||||
|
try {
|
||||||
|
// 准备提交的数据
|
||||||
|
const submitData = {
|
||||||
|
...formData,
|
||||||
|
...values,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是编辑模式,添加id
|
||||||
|
if (isEditMode && articleId) {
|
||||||
|
submitData.articleId = articleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行新增或更新操作
|
||||||
|
if (isEditMode) {
|
||||||
|
await updateShopArticle(submitData);
|
||||||
|
} else {
|
||||||
|
await addShopArticle(submitData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: `${isEditMode ? '更新' : '保存'}成功`,
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.navigateBack();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: `${isEditMode ? '更新' : '保存'}失败`,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitFailed = (error: any) => {
|
||||||
|
console.log(error, 'err...')
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 动态设置页面标题
|
||||||
|
Taro.setNavigationBarTitle({
|
||||||
|
title: isEditMode ? '编辑文章' : '新增文章'
|
||||||
|
});
|
||||||
|
|
||||||
|
reload().then(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [isEditMode]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading className={'px-2'}>加载中</Loading>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form
|
||||||
|
ref={formRef}
|
||||||
|
divider
|
||||||
|
initialValues={formData}
|
||||||
|
labelPosition="left"
|
||||||
|
onFinish={(values) => submitSucceed(values)}
|
||||||
|
onFinishFailed={(errors) => submitFailed(errors)}
|
||||||
|
>
|
||||||
|
{/* 基本信息 */}
|
||||||
|
<CellGroup title="基本信息">
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
label="文章标题"
|
||||||
|
required
|
||||||
|
rules={[{ required: true, message: '请输入文章标题' }]}
|
||||||
|
initialValue={formData.title}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入文章标题" maxLength={100}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="overview" label="文章概述" initialValue={formData.overview}>
|
||||||
|
<TextArea placeholder="请输入文章概述,用于列表展示" maxLength={200} rows={3}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="detail"
|
||||||
|
label="文章内容"
|
||||||
|
required
|
||||||
|
rules={[{ required: true, message: '请输入文章内容' }]}
|
||||||
|
initialValue={formData.detail}
|
||||||
|
>
|
||||||
|
<TextArea placeholder="请输入文章内容" maxLength={10000} rows={8}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="author" label="作者" initialValue={formData.author}>
|
||||||
|
<Input placeholder="请输入作者名称" maxLength={50}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="source" label="来源" initialValue={formData.source}>
|
||||||
|
<Input placeholder="请输入文章来源" maxLength={100}/>
|
||||||
|
</Form.Item>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 文章设置 */}
|
||||||
|
<CellGroup title="文章设置">
|
||||||
|
<Form.Item name="type" label="文章类型" initialValue={formData.type}>
|
||||||
|
<Radio.Group direction="horizontal" value={formData.type}>
|
||||||
|
{typeOptions.map(option => (
|
||||||
|
<Radio key={option.value} value={option.value}>
|
||||||
|
{option.text}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="status" label="发布状态" initialValue={formData.status}>
|
||||||
|
<Radio.Group direction="horizontal" value={formData.status}>
|
||||||
|
{statusOptions.map(option => (
|
||||||
|
<Radio key={option.value} value={option.value}>
|
||||||
|
{option.text}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="permission" label="可见性" initialValue={formData.permission}>
|
||||||
|
<Radio.Group direction="horizontal" value={formData.permission}>
|
||||||
|
{permissionOptions.map(option => (
|
||||||
|
<Radio key={option.value} value={option.value}>
|
||||||
|
{option.text}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="showType" label="显示方式" initialValue={formData.showType}>
|
||||||
|
<Radio.Group direction="horizontal" value={formData.showType}>
|
||||||
|
{showTypeOptions.map(option => (
|
||||||
|
<Radio key={option.value} value={option.value}>
|
||||||
|
{option.text}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 高级设置 */}
|
||||||
|
<CellGroup title="高级设置">
|
||||||
|
<Form.Item name="recommend" label="推荐文章" initialValue={formData.recommend}>
|
||||||
|
<Switch
|
||||||
|
checked={formData.recommend === 1}
|
||||||
|
onChange={(checked) =>
|
||||||
|
setFormData({...formData, recommend: checked ? 1 : 0})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="price" label="付费金额" initialValue={formData.price}>
|
||||||
|
<Input placeholder="0.00" type="number"/>
|
||||||
|
<View className="text-xs text-gray-500 mt-1">单位:元</View>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="virtualViews" label="虚拟阅读量" initialValue={formData.virtualViews}>
|
||||||
|
<InputNumber min={0} defaultValue={formData.virtualViews || 0}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="actualViews" label="实际阅读量" initialValue={formData.actualViews}>
|
||||||
|
<InputNumber min={0} defaultValue={formData.actualViews || 0}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="sortNumber" label="排序" initialValue={formData.sortNumber}>
|
||||||
|
<InputNumber min={0} defaultValue={formData.sortNumber || 0}/>
|
||||||
|
<View className="text-xs text-gray-500 mt-1">数字越小越靠前</View>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="tags" label="标签" initialValue={formData.tags}>
|
||||||
|
<Input placeholder="请输入标签,多个标签用逗号分隔" maxLength={200}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="topic" label="话题" initialValue={formData.topic}>
|
||||||
|
<Input placeholder="请输入话题" maxLength={100}/>
|
||||||
|
</Form.Item>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 图片上传 */}
|
||||||
|
<CellGroup title="文章图片">
|
||||||
|
<Form.Item name="image" label="封面图片" initialValue={formData.image}>
|
||||||
|
<View className="flex items-center gap-3">
|
||||||
|
{formData.image && (
|
||||||
|
<Image
|
||||||
|
src={formData.image}
|
||||||
|
width="80"
|
||||||
|
height="80"
|
||||||
|
radius="8"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
fill="outline"
|
||||||
|
icon={<UploadIcon />}
|
||||||
|
onClick={handleImageUpload}
|
||||||
|
>
|
||||||
|
{formData.image ? '更换图片' : '上传图片'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Form.Item>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 提交按钮 */}
|
||||||
|
<FixedButton text={isEditMode ? '更新文章' : '发布文章'} onClick={submitSucceed} icon={<Edit />} />
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddShopArticle;
|
||||||
@@ -1,151 +1,272 @@
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import Taro, {useDidShow} from '@tarojs/taro'
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
|
import {Button, Cell, CellGroup, Empty, ConfigProvider, SearchBar, Tag, InfiniteLoading, Loading, PullToRefresh} from '@nutui/nutui-react-taro'
|
||||||
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
|
import {Edit, Del, Eye} from '@nutui/icons-react-taro'
|
||||||
import {View} from '@tarojs/components'
|
import {View} from '@tarojs/components'
|
||||||
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
import {ShopArticle} from "@/api/shop/shopArticle/model";
|
||||||
import {listShopUserAddress, removeShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
|
import {pageShopArticle, removeShopArticle} from "@/api/shop/shopArticle";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const Address = () => {
|
const ShopArticleManage = () => {
|
||||||
const [list, setList] = useState<ShopUserAddress[]>([])
|
const [list, setList] = useState<ShopArticle[]>([])
|
||||||
const [address, setAddress] = useState<ShopUserAddress>()
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
const [searchValue, setSearchValue] = useState('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
|
||||||
const reload = () => {
|
const reload = async (isRefresh = false) => {
|
||||||
listShopUserAddress({
|
if (isRefresh) {
|
||||||
userId: Taro.getStorageSync('UserId')
|
setPage(1)
|
||||||
|
setList([])
|
||||||
|
setHasMore(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const currentPage = isRefresh ? 1 : page
|
||||||
|
const res = await pageShopArticle({
|
||||||
|
page: currentPage,
|
||||||
|
limit: 10,
|
||||||
|
keywords: searchValue
|
||||||
})
|
})
|
||||||
.then(data => {
|
|
||||||
setList(data || [])
|
if (res && res.list) {
|
||||||
// 默认地址
|
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||||
setAddress(data.find(item => item.isDefault))
|
setList(newList)
|
||||||
})
|
setTotal(res.count || 0)
|
||||||
.catch(() => {
|
|
||||||
|
// 判断是否还有更多数据
|
||||||
|
setHasMore(res.list.length === 10) // 如果返回的数据等于limit,说明可能还有更多
|
||||||
|
|
||||||
|
if (!isRefresh) {
|
||||||
|
setPage(currentPage + 1)
|
||||||
|
} else {
|
||||||
|
setPage(2) // 刷新后下一页是第2页
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHasMore(false)
|
||||||
|
setTotal(0)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文章失败:', error)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '获取地址失败',
|
title: '获取文章失败',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
});
|
});
|
||||||
})
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDefault = async (item: ShopUserAddress) => {
|
// 搜索功能
|
||||||
if (address) {
|
const handleSearch = (value: string) => {
|
||||||
await updateShopUserAddress({
|
setSearchValue(value)
|
||||||
...address,
|
reload(true)
|
||||||
isDefault: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await updateShopUserAddress({
|
|
||||||
id: item.id,
|
|
||||||
isDefault: true
|
|
||||||
})
|
|
||||||
Taro.showToast({
|
|
||||||
title: '设置成功',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDel = async (id?: number) => {
|
// 下拉刷新
|
||||||
await removeShopUserAddress(id)
|
const handleRefresh = async () => {
|
||||||
|
setRefreshing(true)
|
||||||
|
await reload(true)
|
||||||
|
setRefreshing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除文章
|
||||||
|
const handleDelete = async (id?: number) => {
|
||||||
|
Taro.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除这篇文章吗?',
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
await removeShopArticle(id)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '删除成功',
|
title: '删除成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
reload();
|
reload(true);
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAddress = async (item: ShopUserAddress) => {
|
// 编辑文章
|
||||||
if (address) {
|
const handleEdit = (item: ShopArticle) => {
|
||||||
await updateShopUserAddress({
|
Taro.navigateTo({
|
||||||
...address,
|
url: `/shop/shopArticle/add?id=${item.articleId}`
|
||||||
isDefault: false
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看文章详情
|
||||||
|
const handleView = (item: ShopArticle) => {
|
||||||
|
// 这里可以跳转到文章详情页面
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/cms/detail/index?id=${item.articleId}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await updateShopUserAddress({
|
|
||||||
id: item.id,
|
// 获取状态标签
|
||||||
isDefault: true
|
const getStatusTag = (status?: number) => {
|
||||||
})
|
switch (status) {
|
||||||
setTimeout(() => {
|
case 0:
|
||||||
Taro.navigateBack()
|
return <Tag type="success">已发布</Tag>
|
||||||
},500)
|
case 1:
|
||||||
|
return <Tag type="warning">待审核</Tag>
|
||||||
|
case 2:
|
||||||
|
return <Tag type="danger">已驳回</Tag>
|
||||||
|
case 3:
|
||||||
|
return <Tag type="danger">违规内容</Tag>
|
||||||
|
default:
|
||||||
|
return <Tag>未知</Tag>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const loadMore = async () => {
|
||||||
|
if (!loading && hasMore) {
|
||||||
|
await reload(false) // 不刷新,追加数据
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
reload()
|
reload(true).then()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (list.length == 0) {
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
{/* 搜索栏 */}
|
||||||
height: 'calc(100vh - 300px)',
|
<View className="py-2">
|
||||||
}}>
|
<SearchBar
|
||||||
<Empty
|
placeholder="搜索关键词"
|
||||||
style={{
|
value={searchValue}
|
||||||
backgroundColor: 'transparent'
|
onChange={setSearchValue}
|
||||||
}}
|
onSearch={handleSearch}
|
||||||
description="您还没有地址哦"
|
|
||||||
/>
|
/>
|
||||||
<Space>
|
</View>
|
||||||
<Button onClick={() => Taro.navigateTo({url: '/user/address/add'})}>新增地址</Button>
|
|
||||||
<Button type="success" fill="dashed"
|
|
||||||
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}>获取微信地址</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</ConfigProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
{/* 统计信息 */}
|
||||||
<>
|
{total > 0 && (
|
||||||
<CellGroup>
|
<View className="px-4 py-2 text-sm text-gray-500">
|
||||||
<Cell
|
共找到 {total} 篇文章
|
||||||
onClick={() => Taro.navigateTo({url: '/user/address/wxAddress'})}
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 文章列表 */}
|
||||||
|
<PullToRefresh
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
pullDistance={60}
|
||||||
|
headHeight={60}
|
||||||
>
|
>
|
||||||
<div className={'flex justify-between items-center w-full'}>
|
<View className="px-4" style={{ height: 'calc(100vh - 160px)', overflowY: 'auto' }} id="article-scroll">
|
||||||
<div className={'flex items-center gap-3'}>
|
{list.length === 0 && !loading ? (
|
||||||
<Dongdong className={'text-green-600'}/>
|
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 200px)'}}>
|
||||||
<div>获取微信地址</div>
|
<Empty
|
||||||
</div>
|
description="暂无文章数据"
|
||||||
<ArrowRight className={'text-gray-400'}/>
|
style={{backgroundColor: 'transparent'}}
|
||||||
</div>
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<InfiniteLoading
|
||||||
|
target="article-scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={loadMore}
|
||||||
|
loadingText={
|
||||||
|
<View className="flex justify-center items-center py-4">
|
||||||
|
<Loading />
|
||||||
|
<View className="ml-2">加载中...</View>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
loadMoreText={
|
||||||
|
<View className="text-center py-4 text-gray-500">
|
||||||
|
{list.length === 0 ? "暂无数据" : "没有更多了"}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{list.map((item, index) => (
|
||||||
|
<CellGroup key={item.articleId || index} className="mb-4">
|
||||||
|
<Cell>
|
||||||
|
<View className="flex flex-col gap-3 w-full">
|
||||||
|
{/* 文章标题和状态 */}
|
||||||
|
<View className="flex justify-between items-start">
|
||||||
|
<View className="flex-1 pr-2">
|
||||||
|
<View className="text-lg font-bold text-gray-900 line-clamp-2">
|
||||||
|
{item.title}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{getStatusTag(item.status)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 文章概述 */}
|
||||||
|
{item.overview && (
|
||||||
|
<View className="text-sm text-gray-600 line-clamp-2">
|
||||||
|
{item.overview}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 文章信息 */}
|
||||||
|
<View className="flex justify-between items-center text-xs text-gray-500">
|
||||||
|
<View className="flex items-center gap-4">
|
||||||
|
<View>阅读: {item.actualViews || 0}</View>
|
||||||
|
{item.price && <View>价格: ¥{item.price}</View>}
|
||||||
|
<View>创建: {dayjs(item.createTime).format('MM-DD HH:mm')}</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<View className="flex justify-end gap-2 pt-2 border-t border-gray-100">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
fill="outline"
|
||||||
|
icon={<Eye/>}
|
||||||
|
onClick={() => handleView(item)}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
fill="outline"
|
||||||
|
icon={<Edit/>}
|
||||||
|
onClick={() => handleEdit(item)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
fill="outline"
|
||||||
|
icon={<Del/>}
|
||||||
|
onClick={() => handleDelete(item.articleId)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</Cell>
|
</Cell>
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
{list.map((item, _) => (
|
|
||||||
<Cell.Group>
|
|
||||||
<Cell className={'flex flex-col gap-1'} onClick={() => selectAddress(item)}>
|
|
||||||
<View>
|
|
||||||
<View className={'font-medium text-sm'}>{item.name} {item.phone}</View>
|
|
||||||
</View>
|
|
||||||
<View className={'text-xs'}>
|
|
||||||
{item.province} {item.city} {item.region} {item.address}
|
|
||||||
</View>
|
|
||||||
</Cell>
|
|
||||||
<Cell
|
|
||||||
align="center"
|
|
||||||
title={
|
|
||||||
<View className={'flex items-center gap-1'} onClick={() => onDefault(item)}>
|
|
||||||
{item.isDefault ? <Checked className={'text-green-600'} size={16}/> : <CheckNormal size={16}/>}
|
|
||||||
<View className={'text-gray-400'}>默认地址</View>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
extra={
|
|
||||||
<>
|
|
||||||
<View className={'text-gray-400'} onClick={() => onDel(item.id)}>
|
|
||||||
删除
|
|
||||||
</View>
|
|
||||||
<Divider direction={'vertical'}/>
|
|
||||||
<View className={'text-gray-400'}
|
|
||||||
onClick={() => Taro.navigateTo({url: '/user/address/add?id=' + item.id})}>
|
|
||||||
修改
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Cell.Group>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</InfiniteLoading>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</PullToRefresh>
|
||||||
|
|
||||||
|
{/* 底部浮动按钮 */}
|
||||||
|
<FixedButton
|
||||||
|
text="发布文章"
|
||||||
|
icon={<Edit />}
|
||||||
|
onClick={() => Taro.navigateTo({url: '/shop/shopArticle/add'})}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Address;
|
export default ShopArticleManage;
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import Taro from '@tarojs/taro'
|
|||||||
import {View} from '@tarojs/components'
|
import {View} from '@tarojs/components'
|
||||||
import {Address} from '@nutui/nutui-react-taro'
|
import {Address} from '@nutui/nutui-react-taro'
|
||||||
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||||||
import {getShopUserAddress, listShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
|
import {getShopUserAddress, listShopUserAddress, updateShopUserAddress, addShopUserAddress} from "@/api/shop/shopUserAddress";
|
||||||
import RegionData from '@/api/json/regions-data.json';
|
import RegionData from '@/api/json/regions-data.json';
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
|
||||||
const AddUserAddress = () => {
|
const AddUserAddress = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
@@ -19,13 +20,29 @@ const AddUserAddress = () => {
|
|||||||
const [inputText, setInputText] = useState<string>('')
|
const [inputText, setInputText] = useState<string>('')
|
||||||
const formRef = useRef<any>(null)
|
const formRef = useRef<any>(null)
|
||||||
|
|
||||||
|
// 判断是编辑还是新增模式
|
||||||
|
const isEditMode = !!params.id
|
||||||
|
const addressId = params.id ? Number(params.id) : undefined
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
const address = await getShopUserAddress(Number(params.id))
|
// 整理地区数据
|
||||||
|
setRegionData()
|
||||||
|
|
||||||
|
// 如果是编辑模式,加载地址数据
|
||||||
|
if (isEditMode && addressId) {
|
||||||
|
try {
|
||||||
|
const address = await getShopUserAddress(addressId)
|
||||||
setFormData(address)
|
setFormData(address)
|
||||||
// 设置所在地区
|
// 设置所在地区
|
||||||
setText(`${address.province} ${address.city} ${address.region}`)
|
setText(`${address.province} ${address.city} ${address.region}`)
|
||||||
// 整理地区数据
|
} catch (error) {
|
||||||
setRegionData()
|
console.error('加载地址失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载地址失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,43 +212,72 @@ const AddUserAddress = () => {
|
|||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const submitSucceed = async (values: any) => {
|
const submitSucceed = async (values: any) => {
|
||||||
|
try {
|
||||||
const defaultAddress = await listShopUserAddress({isDefault: true})
|
// 准备提交的数据
|
||||||
if(!defaultAddress) return
|
const submitData = {
|
||||||
|
|
||||||
const setNotDefault = await updateShopUserAddress({
|
|
||||||
...defaultAddress[0],
|
|
||||||
isDefault: false
|
|
||||||
})
|
|
||||||
if(!setNotDefault) return
|
|
||||||
|
|
||||||
updateShopUserAddress({
|
|
||||||
...values,
|
...values,
|
||||||
id: Number(params.id),
|
|
||||||
province: FormData.province,
|
province: FormData.province,
|
||||||
city: FormData.city,
|
city: FormData.city,
|
||||||
region: FormData.region
|
region: FormData.region,
|
||||||
}).then(() => {
|
isDefault: true // 新增或编辑的地址都设为默认地址
|
||||||
Taro.showToast({title: `保存成功`, icon: 'success'})
|
};
|
||||||
setTimeout(() => {
|
|
||||||
return Taro.navigateBack()
|
// 如果是编辑模式,添加id
|
||||||
}, 1000)
|
if (isEditMode && addressId) {
|
||||||
}).catch(() => {
|
submitData.id = addressId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先处理默认地址逻辑
|
||||||
|
const defaultAddress = await listShopUserAddress({isDefault: true});
|
||||||
|
if (defaultAddress && defaultAddress.length > 0) {
|
||||||
|
// 如果当前编辑的不是默认地址,或者是新增地址,需要取消其他默认地址
|
||||||
|
if (!isEditMode || (isEditMode && defaultAddress[0].id !== addressId)) {
|
||||||
|
await updateShopUserAddress({
|
||||||
|
...defaultAddress[0],
|
||||||
|
isDefault: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行新增或更新操作
|
||||||
|
if (isEditMode) {
|
||||||
|
await updateShopUserAddress(submitData);
|
||||||
|
} else {
|
||||||
|
await addShopUserAddress(submitData);
|
||||||
|
}
|
||||||
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '保存失败',
|
title: `${isEditMode ? '更新' : '保存'}成功`,
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.navigateBack();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: `${isEditMode ? '更新' : '保存'}失败`,
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
});
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const submitFailed = (error: any) => {
|
const submitFailed = (error: any) => {
|
||||||
console.log(error, 'err...')
|
console.log(error, 'err...')
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 动态设置页面标题
|
||||||
|
Taro.setNavigationBarTitle({
|
||||||
|
title: isEditMode ? '编辑收货地址' : '新增收货地址'
|
||||||
|
});
|
||||||
|
|
||||||
reload().then(() => {
|
reload().then(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}, []);
|
}, [isEditMode]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading className={'px-2'}>加载中</Loading>
|
return <Loading className={'px-2'}>加载中</Loading>
|
||||||
@@ -246,25 +292,6 @@ const AddUserAddress = () => {
|
|||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
onFinish={(values) => submitSucceed(values)}
|
onFinish={(values) => submitSucceed(values)}
|
||||||
onFinishFailed={(errors) => submitFailed(errors)}
|
onFinishFailed={(errors) => submitFailed(errors)}
|
||||||
footer={
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
nativeType="submit"
|
|
||||||
type="success"
|
|
||||||
size="large"
|
|
||||||
className={'w-full'}
|
|
||||||
block
|
|
||||||
>
|
|
||||||
保存并使用
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<CellGroup className={'px-3'}>
|
<CellGroup className={'px-3'}>
|
||||||
<div
|
<div
|
||||||
@@ -336,6 +363,9 @@ const AddUserAddress = () => {
|
|||||||
}}
|
}}
|
||||||
onClose={() => setVisible(false)}
|
onClose={() => setVisible(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 底部浮动按钮 */}
|
||||||
|
<FixedButton text={isEditMode ? '更新地址' : '保存并使用'} onClick={submitSucceed} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-tar
|
|||||||
import {View} from '@tarojs/components'
|
import {View} from '@tarojs/components'
|
||||||
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||||||
import {listShopUserAddress, removeShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
|
import {listShopUserAddress, removeShopUserAddress, updateShopUserAddress} from "@/api/shop/shopUserAddress";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
|
||||||
const Address = () => {
|
const Address = () => {
|
||||||
const [list, setList] = useState<ShopUserAddress[]>([])
|
const [list, setList] = useState<ShopUserAddress[]>([])
|
||||||
@@ -144,17 +145,8 @@ const Address = () => {
|
|||||||
/>
|
/>
|
||||||
</Cell.Group>
|
</Cell.Group>
|
||||||
))}
|
))}
|
||||||
<View
|
{/* 底部浮动按钮 */}
|
||||||
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
|
<FixedButton text={'新增地址'} onClick={() => Taro.navigateTo({url: '/user/address/add'})} />
|
||||||
<Button
|
|
||||||
type="success"
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
className="px-6"
|
|
||||||
onClick={() => Taro.navigateTo({url: '/user/address/add'})}>
|
|
||||||
新增地址
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {useEffect} from "react";
|
|||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {addShopUserAddress} from "@/api/shop/shopUserAddress";
|
import {addShopUserAddress} from "@/api/shop/shopUserAddress";
|
||||||
|
|
||||||
const Address = () => {
|
const WxAddress = () => {
|
||||||
/**
|
/**
|
||||||
* 从微信API获取用户收货地址
|
* 从微信API获取用户收货地址
|
||||||
* 调用微信原生地址选择界面,获取成功后保存到服务器并刷新列表
|
* 调用微信原生地址选择界面,获取成功后保存到服务器并刷新列表
|
||||||
@@ -64,4 +64,4 @@ const Address = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Address;
|
export default WxAddress;
|
||||||
|
|||||||
Reference in New Issue
Block a user