功能:完善登录安全与客服手动下单
This commit is contained in:
@@ -37,6 +37,8 @@ export const authApi = {
|
||||
const res = await request.get('/auth/profile')
|
||||
return res.data.data as User
|
||||
},
|
||||
changePassword: (data: { current_password: string; new_password: string }) =>
|
||||
request.put('/auth/password', data).then((r) => r.data.data),
|
||||
}
|
||||
|
||||
export const dashboardApi = {
|
||||
@@ -62,6 +64,8 @@ export const merchantApi = {
|
||||
request.patch(`/merchant/products/${id}/status`, { status }).then((r) => r.data.data),
|
||||
orders: (params?: Record<string, unknown>) =>
|
||||
request.get('/merchant/orders', { params }).then((r) => r.data.data as PageResult<FulfillmentOrder>),
|
||||
manualOrderProducts: () =>
|
||||
request.get('/merchant/orders/products').then((r) => r.data.data as MerchantProduct[]),
|
||||
createTestOrder: (data: {
|
||||
sku: string
|
||||
buyer_reference?: string
|
||||
|
||||
@@ -4,11 +4,15 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Layout,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
Space,
|
||||
Typography,
|
||||
Avatar,
|
||||
Tag,
|
||||
Button,
|
||||
message,
|
||||
Tooltip,
|
||||
} from 'antd'
|
||||
import {
|
||||
@@ -18,6 +22,7 @@ import {
|
||||
DashboardOutlined,
|
||||
DownOutlined,
|
||||
FileTextOutlined,
|
||||
KeyOutlined,
|
||||
LogoutOutlined,
|
||||
MenuOutlined,
|
||||
OrderedListOutlined,
|
||||
@@ -27,6 +32,7 @@ import {
|
||||
WalletOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useAuth } from '../store/auth'
|
||||
import { authApi } from '../api'
|
||||
import type { MenuProps } from 'antd'
|
||||
|
||||
const { Header, Sider, Content } = Layout
|
||||
@@ -207,6 +213,9 @@ export default function MainLayout() {
|
||||
return localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === 'true'
|
||||
})
|
||||
const { user, logout, isAdmin, merchant, merchantPermissions } = useAuth()
|
||||
const [passwordOpen, setPasswordOpen] = useState(false)
|
||||
const [passwordSaving, setPasswordSaving] = useState(false)
|
||||
const [passwordForm] = Form.useForm()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const isDocsPage = location.pathname.startsWith('/open-api')
|
||||
@@ -265,6 +274,25 @@ export default function MainLayout() {
|
||||
navigate(child.path)
|
||||
}
|
||||
|
||||
const submitPasswordChange = async () => {
|
||||
try {
|
||||
const values = await passwordForm.validateFields()
|
||||
setPasswordSaving(true)
|
||||
await authApi.changePassword({
|
||||
current_password: values.current_password,
|
||||
new_password: values.new_password,
|
||||
})
|
||||
setPasswordOpen(false)
|
||||
passwordForm.resetFields()
|
||||
logout()
|
||||
navigate('/login')
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '密码修改失败')
|
||||
} finally {
|
||||
setPasswordSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const userMenu: MenuProps['items'] = [
|
||||
{
|
||||
key: 'user-info',
|
||||
@@ -277,6 +305,12 @@ export default function MainLayout() {
|
||||
),
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'change-password',
|
||||
icon: <KeyOutlined />,
|
||||
label: '修改密码',
|
||||
onClick: () => setPasswordOpen(true),
|
||||
},
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined style={{ color: '#dc2626' }} />,
|
||||
@@ -412,6 +446,32 @@ export default function MainLayout() {
|
||||
<Outlet />
|
||||
</div>
|
||||
</Content>
|
||||
<Modal
|
||||
title="修改密码"
|
||||
open={passwordOpen}
|
||||
confirmLoading={passwordSaving}
|
||||
onOk={submitPasswordChange}
|
||||
onCancel={() => {
|
||||
setPasswordOpen(false)
|
||||
passwordForm.resetFields()
|
||||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={passwordForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item name="current_password" label="当前密码" rules={[{ required: true, message: '请输入当前密码' }]}>
|
||||
<Input.Password autoComplete="current-password" />
|
||||
</Form.Item>
|
||||
<Form.Item name="new_password" label="新密码" rules={[{ required: true, min: 8, message: '新密码至少 8 位' }]}>
|
||||
<Input.Password autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
<Form.Item name="confirm_password" label="确认新密码" dependencies={['new_password']} rules={[
|
||||
{ required: true, message: '请再次输入新密码' },
|
||||
({ getFieldValue }) => ({ validator: (_, value) => !value || getFieldValue('new_password') === value ? Promise.resolve() : Promise.reject(new Error('两次输入的密码不一致')) }),
|
||||
]}>
|
||||
<Input.Password autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Form, Input, Button, Typography, message, Space, Tag } from 'antd'
|
||||
import { Form, Input, Button, Typography, message, Space } from 'antd'
|
||||
import { UserOutlined, LockOutlined, RightOutlined } from '@ant-design/icons'
|
||||
import { useAuth } from '../store/auth'
|
||||
|
||||
@@ -23,11 +23,6 @@ export default function Login() {
|
||||
}
|
||||
}
|
||||
|
||||
const fillCredentials = (user: string, pass: string) => {
|
||||
form.setFieldsValue({ username: user, password: pass })
|
||||
message.info(`已填入账号: ${user}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-wrapper">
|
||||
<div className="login-card">
|
||||
@@ -46,7 +41,6 @@ export default function Login() {
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
initialValues={{ username: 'admin', password: 'admin123' }}
|
||||
requiredMark={false}
|
||||
>
|
||||
<Form.Item
|
||||
@@ -97,33 +91,10 @@ export default function Login() {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
{/* 快捷测试账号填充 */}
|
||||
<div
|
||||
style={{
|
||||
padding: '12px 14px',
|
||||
borderRadius: 8,
|
||||
background: 'rgba(255, 255, 255, 0.04)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, color: '#94a3b8', marginBottom: 8, textAlign: 'center' }}>
|
||||
快速体验(演示账号列表):
|
||||
</div>
|
||||
<Space style={{ width: '100%', justifyContent: 'center' }} wrap>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{ cursor: 'pointer', padding: '4px 10px', borderRadius: 4 }}
|
||||
onClick={() => fillCredentials('admin', 'admin123')}
|
||||
>
|
||||
管理员 (admin)
|
||||
</Tag>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Typography.Paragraph
|
||||
style={{ fontSize: 12, margin: 0, textAlign: 'center', color: '#64748b' }}
|
||||
>
|
||||
商户账号与成员密钥请联系平台管理员统一配置与创建
|
||||
请使用平台管理员或商户负责人分配的账号登录
|
||||
</Typography.Paragraph>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
@@ -294,25 +294,18 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
||||
})
|
||||
}, [loadWallet, merchantPermissions, walletFilterForm])
|
||||
|
||||
const newManualClientOrderNo = () => {
|
||||
const suffix = window.crypto?.randomUUID?.().replaceAll('-', '') || `${Date.now()}${Math.random().toString(36).slice(2)}`
|
||||
return `manual-${suffix.slice(0, 32)}`
|
||||
}
|
||||
|
||||
const openManualOrderCreate = () => {
|
||||
manualOrderForm.resetFields()
|
||||
manualOrderForm.setFieldsValue({
|
||||
client_order_no: newManualClientOrderNo(),
|
||||
quantity: 1,
|
||||
})
|
||||
setManualOrderOpen(true)
|
||||
setManualOrderProductsLoading(true)
|
||||
merchantApi.products({ page: 1, size: 100 })
|
||||
merchantApi.manualOrderProducts()
|
||||
.then((data) => {
|
||||
const activeProducts = (data.list || []).filter((item) => item.status === 'active')
|
||||
setManualOrderProducts(activeProducts)
|
||||
if (activeProducts.length > 0) {
|
||||
manualOrderForm.setFieldsValue({ sku: activeProducts[0].sku })
|
||||
setManualOrderProducts(data || [])
|
||||
if (data.length > 0) {
|
||||
manualOrderForm.setFieldsValue({ sku: data[0].sku })
|
||||
} else {
|
||||
message.warning('没有可用商品')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user