使用钉钉OAuth登陆系统

会话为关联订单提示
订单列表增加空状态
dev/mobile
Jimmy Liow 2 years ago
parent b222a1245e
commit 66e7ad17d1

@ -1,14 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { AuthContext } from '@/stores/AuthContext'
import { ThemeContext } from '@/stores/ThemeContext'
import AuthApp from '@/views/AuthApp'
import Standlone from '@/views/Standlone'
import OrderFollow from '@/views/OrderFollow'
import ChatHistory from '@/views/ChatHistory'
import SalesManagement from '@/views/SalesManagement'
import DingdingQRCode from '@/views/DingdingQRCode'
import DingdingQRCode from '@/views/dingding/QRCode'
import DingdingCallback from '@/views/dingding/Callback'
import AccountProfile from '@/views/AccountProfile'
import ErrorPage from '@/components/ErrorPage'
import Conversations from '@/views/Conversations/ChatWindow'
@ -37,6 +37,7 @@ const router = createBrowserRouter([
element: <Standlone />,
children: [
{ path: 'dingding/qrcode', element: <DingdingQRCode /> },
{ path: 'dingding/callback', element: <DingdingCallback /> },
],
},
])

@ -1,7 +0,0 @@
import { createContext, useContext } from 'react'
export const AuthContext = createContext({})
export function useAuthContext() {
return useContext(AuthContext)
}

@ -1,5 +1,5 @@
import { LinkOutlined, MailOutlined, PhoneOutlined, UserOutlined, WhatsAppOutlined } from '@ant-design/icons'
import { Card, Flex, Select, Typography } from 'antd'
import { Card, Flex, Select, Typography, Empty, Button } from 'antd'
import { useEffect } from 'react'
import { useParams } from 'react-router-dom'
@ -19,67 +19,81 @@ const CustomerProfile = (() => {
let regularText = ''
if (orderDetail.buytime > 0) regularText = '(R' + orderDetail.buytime + ')'
return (
<div className=' divide-x-0 divide-y divide-dashed divide-gray-300 '>
<Card className='p-2 '
bordered={false}
title={orderDetail.order_no}
actions={[
<Select
style={{
width: '100%'
}}
variant='borderless'
onSelect={(value) => {
setOrderPropValue(order_sn, 'orderlabel', value)
}}
value={orderDetail.tags}
options={[
{ value: 0, label: '未设置', disabled: true, },
{ value: 240003, label: '重点' },
{ value: 240002, label: '次重点' },
{ value: 240001, label: '一般' }
]}
/>,
<Select
style={{
width: '100%'
}}
variant='borderless'
onSelect={(value) => {
setOrderPropValue(order_sn,'orderstatus', value)
}}
value={orderDetail.states}
options={[
{ value: 1, label: '新订单' },
{ value: 2, label: '报价中' },
{ value: 3, label: '以后联系' },
{ value: 4, label: '等待付订金' },
{ value: 5, label: '成行' },
{ value: 6, label: '丢失' },
{ value: 7, label: '取消' }
]}
/>
]}
>
<Flex gap={10}>
<Flex vertical={true} justify='space-between'>
<Typography.Text ><UserOutlined className=' pr-1' />{customerDetail.name + regularText}</Typography.Text>
<Typography.Text ><PhoneOutlined className=' pr-1' />{customerDetail.phone}</Typography.Text>
<Typography.Text ><MailOutlined className=' pr-1' />{customerDetail.email}</Typography.Text>
<Typography.Text ><WhatsAppOutlined className='pr-1' />{customerDetail.whatsapp_phone_number}</Typography.Text>
if (order_sn) {
return (
<div className=' divide-x-0 divide-y divide-dashed divide-gray-300 '>
<Card className='p-2 '
bordered={false}
title={orderDetail.order_no}
actions={[
<Select
style={{
width: '100%'
}}
variant='borderless'
onSelect={(value) => {
setOrderPropValue(order_sn, 'orderlabel', value)
}}
value={orderDetail.tags}
options={[
{ value: 0, label: '未设置', disabled: true, },
{ value: 240003, label: '重点' },
{ value: 240002, label: '次重点' },
{ value: 240001, label: '一般' }
]}
/>,
<Select
style={{
width: '100%'
}}
variant='borderless'
onSelect={(value) => {
setOrderPropValue(order_sn,'orderstatus', value)
}}
value={orderDetail.states}
options={[
{ value: 1, label: '新订单' },
{ value: 2, label: '报价中' },
{ value: 3, label: '以后联系' },
{ value: 4, label: '等待付订金' },
{ value: 5, label: '成行' },
{ value: 6, label: '丢失' },
{ value: 7, label: '取消' }
]}
/>
]}
>
<Flex gap={10}>
<Flex vertical={true} justify='space-between'>
<Typography.Text ><UserOutlined className=' pr-1' />{customerDetail.name + regularText}</Typography.Text>
<Typography.Text ><PhoneOutlined className=' pr-1' />{customerDetail.phone}</Typography.Text>
<Typography.Text ><MailOutlined className=' pr-1' />{customerDetail.email}</Typography.Text>
<Typography.Text ><WhatsAppOutlined className='pr-1' />{customerDetail.whatsapp_phone_number}</Typography.Text>
</Flex>
</Flex>
</Card>
<Flex vertical={true} className='p-2 '>
<Typography.Text strong>最新报价</Typography.Text>
<p className='m-0 py-2 line-clamp-2 '><a target='_blank' href={lastQuotation.letterurl}><LinkOutlined />&nbsp;{lastQuotation.lettertitle}</a></p>
<Flex justify={'space-between'} >
<QuotesHistory dataSource={quotationList} />
</Flex>
</Flex>
</Card>
<Flex vertical={true} className='p-2 '>
<Typography.Text strong>最新报价</Typography.Text>
<p className='m-0 py-2 line-clamp-2 '><a target='_blank' href={lastQuotation.letterurl}><LinkOutlined />&nbsp;{lastQuotation.lettertitle}</a></p>
<Flex justify={'space-between'} >
<QuotesHistory dataSource={quotationList} />
</Flex>
</Flex>
<p className='p-2 shadow-inner overflow-auto m-0 break-words whitespace-pre-wrap ' dangerouslySetInnerHTML={{__html: orderDetail.order_detail}}></p>
</div>
)
<p className='p-2 shadow-inner overflow-auto m-0 break-words whitespace-pre-wrap ' dangerouslySetInnerHTML={{__html: orderDetail.order_detail}}></p>
</div>
)
} else {
return (
<Empty
description={
<span>
暂无相关订单
</span>
}
>
<Button type='primary'>现在关联</Button>
</Empty>
)
}
})
export default CustomerProfile

@ -1,5 +1,5 @@
import {
App, Badge, Button, DatePicker, Tabs, Flex, Form, Input,
App, Badge, Button, DatePicker, Tabs, Flex, Form, Input, Empty,
Radio, Row, Col, Select, Space, Switch, Table, Tag, Tooltip
} from 'antd'
import { InfoCircleTwoTone, MessageTwoTone, PhoneTwoTone, MailTwoTone, WhatsAppOutlined } from '@ant-design/icons'
@ -320,8 +320,13 @@ function OrderGroupTable({ formValues }) {
}
)
})
return (<Conditional
condition={orderList.length > 0}
whenTrue={<Tabs defaultActiveKey={0} items={collapseItems} />}
whenFalse={<Empty />}
/>)
return (<Tabs defaultActiveKey={0} items={collapseItems} />)
// return (<Collapse bordered={false} defaultActiveKey={0} items={collapseItems} />)
}

@ -1,26 +1,14 @@
import '@/assets/App.css'
import AppLogo from '@/assets/logo-gh.png'
import useAuthStore from '@/stores/AuthStore'
import { useThemeContext } from '@/stores/ThemeContext'
import { App as AntApp, Col, ConfigProvider, Empty, Layout, Row, Typography, theme } from 'antd'
import { NavLink, Outlet, useHref, useNavigate } from 'react-router-dom'
import { useEffect } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
const { Header, Footer, Content } = Layout
const { Title } = Typography
function Standlone() {
const {colorPrimary, borderRadius} = useThemeContext()
const { loginUser } = useAuthStore()
const navigate = useNavigate()
const href = useHref()
useEffect(() => {
// /p...
if (loginUser.userId > 0) {
navigate('/');
}
}, [href])
const {
token: { colorBgContainer },

@ -0,0 +1,72 @@
import useAuthStore from '@/stores/AuthStore'
import { isNotEmpty } from '@/utils/commons'
import { Flex, Result, Spin, Typography } from 'antd'
import { useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
const { Title } = Typography
// https://open.dingtalk.com/document/orgapp/obtain-identity-credentials#title-4up-u8w-5ug
// OAuth
// https://login.dingtalk.com/oauth2/auth?redirect_uri=https%3A%2F%2Fsales.mycht.cn%2Fp%2Fdingding%2Fcallback&response_type=code&client_id=dingwgdx6emlxr3fcrg8&scope=openid&state=global-saels&prompt=consent
function Callback() {
const navigate = useNavigate()
const { loginStatus, login, logout, setLoginStatus } = useAuthStore()
const urlSearch = new URLSearchParams(location.search)
const authCode = urlSearch.get('authCode')
const state = urlSearch.get('state')
const error = urlSearch.get('error')
useEffect (() => {
if (isNotEmpty(authCode) && state === 'global-saels') {
login(authCode)
} else {
console.error('error: ' + error)
}
}, [])
if (loginStatus === 200) {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Result
status='success'
title='扫码成功'
subTitle='正在获取你的权限'
extra={[
<Spin size='small' />
]}
/>
</Flex>
)
} else if (loginStatus === 302) {
navigate('/')
} else if (loginStatus === 403) {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Result
status='403'
title='403'
subTitle='你没有绑定钉钉账号,无法登陆。'
/>
</Flex>
)
} else {
return (
<Flex justify='center' align='center' gap='middle' vertical>
<Result
status='success'
title='登陆成功'
subTitle='正在获取你的权限'
extra={[
<Spin size='small' />
]}
/>
</Flex>
)
}
}
export default Callback

@ -1,17 +1,24 @@
import useAuthStore from '@/stores/AuthStore'
import { Flex, Result, Spin, Typography } from 'antd'
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate, useHref } from 'react-router-dom'
const { Title } = Typography
// https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information#title-qpi-0qv-anm
function DingdingQRCode() {
function QRCode() {
const navigate = useNavigate()
const href = useHref()
const { loginStatus, login, logout, setLoginStatus } = useAuthStore()
const { loginStatus, loginUser, login, logout, setLoginStatus } = useAuthStore()
useEffect(() => {
if (loginUser.userId > 0) {
navigate('/');
}
}, [href])
useEffect (() => {
if (location.search === '?out') {
@ -83,4 +90,4 @@ function DingdingQRCode() {
}
}
export default DingdingQRCode
export default QRCode
Loading…
Cancel
Save