Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/JiaShiDa
| | |
| | | |
| | | # production |
| | | /dist |
| | | /admin |
| | | /management |
| | | |
| | | # misc |
| | | .DS_Store |
| | |
| | | const { REACT_APP_ENV = 'dev' } = process.env; |
| | | |
| | | export default defineConfig({ |
| | | outputPath: 'admin', // 设置打包后的文件名为 screen |
| | | outputPath: 'management', |
| | | /** |
| | | * @name 开启 hash 模式 |
| | | * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 |
| | |
| | | mfsu: { |
| | | strategy: 'normal', |
| | | }, |
| | | favicons: [], |
| | | favicons: ['https://jiashikang.cn/images/2025-12-26/30ae8143-dc90-49b7-bc1f-ac49b74ae092.png'], |
| | | requestRecord: {}, |
| | | publicPath: REACT_APP_ENV === 'prod' ? '/manage' : '/', |
| | | publicPath: '/', |
| | | }); |
| | |
| | | export default { |
| | | dev: { |
| | | SERVER_URL: 'http://192.168.110.111:9908', |
| | | IMAGE_URL: 'http://221.182.45.100:11223/images/', // 图片地址 |
| | | SERVER_URL: 'https://jiashikang.cn/manage', |
| | | IMAGE_URL: 'https://jiashikang.cn/images/', // 图片地址 |
| | | }, |
| | | test: { |
| | | SERVER_URL: 'http://221.182.45.100:8686', |
| | | IMAGE_URL: 'https://jiashikang.cn/images/', // 图片地址 |
| | | }, |
| | | prod: { |
| | | SERVER_URL: 'http://47.108.162.242:8090', |
| | | SERVER_URL: 'https://jiashikang.cn/manage', |
| | | IMAGE_URL: 'https://jiashikang.cn/images/', // 图片地址 |
| | | }, |
| | | }; |
| | |
| | | "private": true, |
| | | "description": "An out-of-box UI solution for enterprise applications", |
| | | "scripts": { |
| | | "start:dev": "cross-env PORT=8888 REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev", |
| | | "start:test": "cross-env PORT=8888 REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", |
| | | "start:prod": "cross-env PORT=8888 REACT_APP_ENV=prod MOCK=none UMI_ENV=prod max dev", |
| | | "build:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max build", |
| | | "build:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=test max build", |
| | | "build:prod": "cross-env REACT_APP_ENV=prod MOCK=none UMI_ENV=prod max build", |
| | | "analyze": "cross-env ANALYZE=1 max build", |
| | | "build": "cross-env REACT_APP_ENV=prod MOCK=none UMI_ENV=prod max build", |
| | | "deploy": "npm run build && npm run gh-pages", |
| | | "dev": "npm run start:dev --debug", |
| | | "gh-pages": "gh-pages -d dist", |
| | | "postinstall": "max setup", |
| | | "jest": "jest", |
| | |
| | | "preview": "npm run build && max preview --port 8000", |
| | | "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login", |
| | | "serve": "umi-serve", |
| | | "start": "cross-env PORT=8888 UMI_ENV=dev max dev", |
| | | "start:dev": "cross-env PORT=8888 REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev", |
| | | "start:test": "cross-env PORT=8888 REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", |
| | | "start:pre": "cross-env PORT=8888 REACT_APP_ENV=pre UMI_ENV=dev max dev", |
| | | "start:prod": "cross-env PORT=8888 REACT_APP_ENV=prod MOCK=none UMI_ENV=prod max dev", |
| | | "test": "jest", |
| | | "test:coverage": "npm run jest -- --coverage", |
| | | "test:update": "npm run jest -- -u", |
| | |
| | | loading?: boolean; |
| | | }> { |
| | | // 如果不是登录页面,执行 |
| | | let permission = []; |
| | | if ( |
| | | JSON.parse(localStorage.getItem('userInfo') || '').userId && |
| | | window.location.pathname !== loginPath |
| | | ) { |
| | | const roleInfo = await getRoleInfoFromUserId( |
| | | JSON.parse(localStorage.getItem('userInfo') || '').userId, |
| | | ); |
| | | const allMenuIds = getAllMenuIds(roleInfo.data || []); |
| | | const { location } = history; |
| | | if (location.pathname !== loginPath && !localStorage.getItem('userInfo')) { |
| | | history.replace(loginPath) |
| | | |
| | | // 将 menuId 转为权限对象 |
| | | const accessObj: any = {}; |
| | | allMenuIds.forEach((id) => { |
| | | accessObj[id] = true; |
| | | }); |
| | | permission = accessObj; |
| | | return { |
| | | permission: JSON.parse(localStorage.getItem('access') || '') || [], |
| | | currentUser: JSON.parse(localStorage.getItem('userInfo') || '') || {}, |
| | | settings: { ...defaultSettings, fixedHeader: true } as Partial<LayoutSettings>, |
| | | }; |
| | | } |
| | | |
| | | return { |
| | |
| | | import { ProTable, Modal, message } from '@ant-design/pro-components'; |
| | | import { ProTable, Modal } from '@ant-design/pro-components'; |
| | | import { useRef, useState } from 'react'; |
| | | import { Button, Space } from 'antd'; |
| | | import { Button, Space, message } from 'antd'; |
| | | import { getRefundOrderList } from './service'; |
| | | import OrderDetailModal from '../order/bigGiftPackageOrder/components/OrderDetailModal'; |
| | | import RefundApplyModal from './components/RefundApplyModal'; |
| | |
| | | import { Button, Modal, Space, Typography } from 'antd'; |
| | | import { Button, Modal, Space, Typography, Spin } from 'antd'; |
| | | import { forwardRef, useImperativeHandle, useState } from 'react'; |
| | | import { DownloadOutlined } from '@ant-design/icons'; |
| | | import { request } from '@umijs/max'; |
| | | import { getUserQrcode } from '../service'; |
| | | |
| | | const { Title, Paragraph } = Typography; |
| | | |
| | | const QrCodeModal = ({ visible, onCancel, userInfo }, ref) => { |
| | | const QrCodeModal = ({ visible, onCancel }, ref) => { |
| | | const [loading, setLoading] = useState(false); |
| | | const [qrCodeUrl, setQrCodeUrl] = useState(''); |
| | | const [qrCodeLoading, setQrCodeLoading] = useState(false); |
| | | const [userInfoData, setUserInfoData] = useState({}); |
| | | |
| | | useImperativeHandle(ref, () => { |
| | | return { |
| | | showQrCode: (url) => { |
| | | setQrCodeUrl(url); |
| | | setVisible(true); |
| | | showQrCode: (userInfo) => { |
| | | setUserInfoData(userInfo) |
| | | fetchQrCode(userInfo); |
| | | }, |
| | | }; |
| | | }); |
| | | |
| | | // 获取二维码 |
| | | const fetchQrCode = async (userInfo) => { |
| | | if (!userInfo?.userId) return; |
| | | |
| | | setQrCodeLoading(true); |
| | | try { |
| | | const response = await getUserQrcode({ userId: userInfo.userId }); |
| | | if (response && response.code === 200) { |
| | | setQrCodeUrl(response.data); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取二维码失败:', error); |
| | | message.error('获取二维码失败'); |
| | | } finally { |
| | | setQrCodeLoading(false); |
| | | } |
| | | }; |
| | | |
| | | const downloadQrCode = async () => { |
| | | if (!qrCodeUrl) return; |
| | | |
| | | setLoading(true); |
| | | try { |
| | | const response = await request(qrCodeUrl, { |
| | | method: 'GET', |
| | | responseType: 'blob', |
| | | }); |
| | | // 将base64转换为blob并下载 |
| | | const byteCharacters = atob(qrCodeUrl); |
| | | const byteNumbers = new Array(byteCharacters.length); |
| | | |
| | | const url = window.URL.createObjectURL(new Blob([response])); |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i); |
| | | } |
| | | |
| | | const byteArray = new Uint8Array(byteNumbers); |
| | | const blob = new Blob([byteArray], { type: 'image/png' }); |
| | | |
| | | const url = window.URL.createObjectURL(blob); |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.setAttribute('download', `推广二维码_${userInfo.nickName || userInfo.code || '未命名'}.png`); |
| | |
| | | window.URL.revokeObjectURL(url); |
| | | } catch (error) { |
| | | console.error('下载二维码失败:', error); |
| | | message.error('下载二维码失败'); |
| | | } finally { |
| | | setLoading(false); |
| | | } |
| | |
| | | } |
| | | > |
| | | <div style={{ textAlign: 'center', padding: '20px 0' }}> |
| | | <Title level={4}>{userInfo.nickName || userInfo.code} 的推广二维码</Title> |
| | | {qrCodeUrl ? ( |
| | | <Title level={4}>{userInfoData.nickName || userInfoData.code} 的推广二维码</Title> |
| | | {qrCodeLoading ? ( |
| | | <div style={{ marginTop: 20 }}> |
| | | <Spin /> |
| | | </div> |
| | | ) : qrCodeUrl ? ( |
| | | <div style={{ marginTop: 20 }}> |
| | | <img |
| | | src={qrCodeUrl} |
| | | src={`data:image/png;base64,${qrCodeUrl}`} |
| | | alt="推广二维码" |
| | | style={{ maxWidth: '100%', height: 'auto', border: '1px solid #d9d9d9', borderRadius: 4 }} |
| | | /> |
| | |
| | | import { |
| | | buildProTableDataSource, |
| | | sendRequest, |
| | | showConfirm, |
| | | showDelConfirm, |
| | | } from '@/utils/antdUtils'; |
| | | import { importFile } from '@/utils/utils'; |
| | | import { PageContainer, ProTable } from '@ant-design/pro-components'; |
| | | import { ProTable } from '@ant-design/pro-components'; |
| | | import { Button, Space, Tag, Upload, message } from 'antd'; |
| | | import { useRef, useState } from 'react'; |
| | | import UserModal from './userModal'; |
| | |
| | | const qrCodeModalRef = useRef(); |
| | | const [userModalVisible, setUserModalVisible] = useState(false); |
| | | const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false); |
| | | const [currentUser, setCurrentUser] = useState({}); |
| | | const [importLoading, setImportLoading] = useState(false); |
| | | const [fileList, setFileList] = useState([]); |
| | | |
| | |
| | | size="small" |
| | | type="link" |
| | | onClick={() => { |
| | | setCurrentUser(record); |
| | | userModalRef.current.setViewMode('view'); |
| | | userModalRef.current.refreshData(record); |
| | | setUserModalVisible(true); |
| | |
| | | size="small" |
| | | type="link" |
| | | onClick={() => { |
| | | setCurrentUser(record); |
| | | userModalRef.current.setViewMode('edit'); |
| | | userModalRef.current.refreshData(record); |
| | | setUserModalVisible(true); |
| | |
| | | size="small" |
| | | type="link" |
| | | onClick={() => { |
| | | setCurrentUser(record); |
| | | qrCodeModalRef.current.showQrCode(record); |
| | | setQrCodeModalVisible(true); |
| | | }} |
| | | > |
| | |
| | | <Button |
| | | type='primary' |
| | | onClick={() => { |
| | | setCurrentUser({}); |
| | | userModalRef.current.setViewMode('add'); |
| | | userModalRef.current.refreshData({}); |
| | | setUserModalVisible(true); |
| | |
| | | ref={qrCodeModalRef} |
| | | visible={qrCodeModalVisible} |
| | | onCancel={() => setQrCodeModalVisible(false)} |
| | | userInfo={currentUser} |
| | | /> |
| | | </div> |
| | | ); |
| | |
| | | return request(`/t-company/system/user/deleteById/${id}`, { |
| | | method: 'DELETE', |
| | | }); |
| | | }; |
| | | |
| | | // 管理人员获取二维码 |
| | | export const getUserQrcode = async (params) => { |
| | | return request('/t-company/getQrcode', { |
| | | method: 'GET', |
| | | params, |
| | | }); |
| | | }; |
| | |
| | | import { ProTable, Modal } from '@ant-design/pro-components'; |
| | | import { ProTable } from '@ant-design/pro-components'; |
| | | import { useRef, useState } from 'react'; |
| | | import { Button, Space, DownloadOutlined } from 'antd'; |
| | | import { getCompanyUserList } from '../service'; |
| | | import { Button, Space, Modal, Spin } from 'antd'; |
| | | import { getCompanyUserList, getUserQrcode } from '../service'; |
| | | import { buildProTableDataSource } from '@/utils/antdUtils'; |
| | | import { request } from '@umijs/max'; |
| | | |
| | |
| | | const [qrCodeUrl, setQrCodeUrl] = useState(''); |
| | | const [currentRecord, setCurrentRecord] = useState(null); |
| | | const [loading, setLoading] = useState(false); |
| | | const [qrCodeLoading, setQrCodeLoading] = useState(false); |
| | | |
| | | // 获取二维码 |
| | | const fetchQrCode = async (record) => { |
| | | setCurrentRecord(record); |
| | | if (!record?.userId) return; |
| | | |
| | | setQrCodeLoading(true); |
| | | try { |
| | | const response = await getUserQrcode({ userId: record.userId }); |
| | | if (response && response.code === 200) { |
| | | setQrCodeUrl(response.data); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取二维码失败:', error); |
| | | message.error('获取二维码失败'); |
| | | } finally { |
| | | setQrCodeLoading(false); |
| | | } |
| | | }; |
| | | |
| | | // 下载二维码 |
| | | const downloadQrCode = async () => { |
| | |
| | | |
| | | setLoading(true); |
| | | try { |
| | | const response = await request(qrCodeUrl, { |
| | | method: 'GET', |
| | | responseType: 'blob', |
| | | }); |
| | | // 将base64转换为blob并下载 |
| | | const byteCharacters = atob(qrCodeUrl); |
| | | const byteNumbers = new Array(byteCharacters.length); |
| | | |
| | | const url = window.URL.createObjectURL(new Blob([response])); |
| | | for (let i = 0; i < byteCharacters.length; i++) { |
| | | byteNumbers[i] = byteCharacters.charCodeAt(i); |
| | | } |
| | | |
| | | const byteArray = new Uint8Array(byteNumbers); |
| | | const blob = new Blob([byteArray], { type: 'image/png' }); |
| | | |
| | | const url = window.URL.createObjectURL(blob); |
| | | const link = document.createElement('a'); |
| | | link.href = url; |
| | | link.setAttribute('download', `推广二维码_${currentRecord?.nickName || currentRecord?.code || '未命名'}.png`); |
| | |
| | | window.URL.revokeObjectURL(url); |
| | | } catch (error) { |
| | | console.error('下载二维码失败:', error); |
| | | message.error('下载二维码失败'); |
| | | } finally { |
| | | setLoading(false); |
| | | } |
| | |
| | | type="link" |
| | | size="small" |
| | | onClick={() => { |
| | | setCurrentRecord(record); |
| | | setQrCodeUrl(`/t-company-user/qrCode/${record.id}`); |
| | | setQrCodeUrl(''); |
| | | setQrVisible(true); |
| | | fetchQrCode(record) |
| | | }} |
| | | > |
| | | 推广二维码 |
| | |
| | | return ( |
| | | <> |
| | | <ProTable |
| | | rowKey="id" |
| | | rowKey="userId" |
| | | columns={columns} |
| | | actionRef={actionRef} |
| | | cardBordered |
| | |
| | | <Modal |
| | | width={500} |
| | | centered |
| | | destroyOnClose |
| | | destroyOnHidden |
| | | title="推广二维码" |
| | | open={qrVisible} |
| | | onCancel={() => setQrVisible(false)} |
| | |
| | | {qrCodeUrl && ( |
| | | <Button |
| | | type="primary" |
| | | icon={<DownloadOutlined />} |
| | | loading={loading} |
| | | onClick={downloadQrCode} |
| | | > |
| | |
| | | > |
| | | <div style={{ textAlign: 'center', padding: '20px 0' }}> |
| | | <h3>{currentRecord?.nickName || currentRecord?.code} 的推广二维码</h3> |
| | | {qrCodeUrl ? ( |
| | | {qrCodeLoading ? ( |
| | | <div style={{ marginTop: 20 }}> |
| | | <Spin /> |
| | | </div> |
| | | ) : qrCodeUrl ? ( |
| | | <div style={{ marginTop: 20 }}> |
| | | <img |
| | | src={qrCodeUrl} |
| | | src={`data:image/png;base64,${qrCodeUrl}`} |
| | | alt="推广二维码" |
| | | style={{ maxWidth: '100%', height: 'auto', border: '1px solid #d9d9d9', borderRadius: 4 }} |
| | | /> |
| | |
| | | method: 'POST', |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | // 管理人员获取二维码 |
| | | export const getUserQrcode = async (params) => { |
| | | return request('/t-company/getQrcode', { |
| | | method: 'GET', |
| | | params, |
| | | }); |
| | | }; |
| | |
| | | onCancel={onCancel} |
| | | footer={null} |
| | | width={1200} |
| | | destroyOnClose |
| | | destroyOnHidden |
| | | > |
| | | {loading ? ( |
| | | <div style={{ textAlign: 'center', padding: '50px' }}>加载中...</div> |
| | |
| | | { |
| | | title: '订单编号', |
| | | dataIndex: 'code', |
| | | hideInSearch: true, |
| | | key: 'code', |
| | | }, |
| | | { |
| | |
| | | { |
| | | title: '下单用户', |
| | | dataIndex: 'userName', |
| | | hideInSearch: true, |
| | | key: 'userName', |
| | | }, |
| | | { |
| | | title: '用户手机号', |
| | | dataIndex: 'phone', |
| | | hideInSearch: true, |
| | | key: 'phone', |
| | | }, |
| | | { |
| | |
| | | break; |
| | | // 启动页图片 |
| | | case 9: |
| | | // let imageUrl = ''; |
| | | // if (item.content.startsWith('http://') || item.content.startsWith('https://')) { |
| | | // imageUrl = item.content; |
| | | // } else { |
| | | // // 确保路径格式正确(去除开头的斜杠,因为IMAGE_URL已经包含斜杠) |
| | | // item.content = item.content.replace(/^\/+/, ''); |
| | | // imageUrl = item.content; |
| | | // } |
| | | setSplashScreenImage(item.content || ''); |
| | | break; |
| | | } |
| | |
| | | |
| | | // 图片上传成功回调 |
| | | const handleUploadSuccess = (response) => { |
| | | setSplashScreenImage(response); |
| | | setSplashScreenImage(IMAGE_URL + response); |
| | | }; |
| | | |
| | | // 图片移除回调 |
| | |
| | | > |
| | | {splashScreenImage ? ( |
| | | <img |
| | | src={IMAGE_URL + splashScreenImage} |
| | | src={splashScreenImage} |
| | | alt="启动页图片" |
| | | style={{ width: '100%', height: '100%', objectFit: 'cover' }} |
| | | /> |
| | |
| | | exportExcell('用户列表.xlsx', searchParams, '/t-app-user/exportAppUser'); |
| | | }; |
| | | |
| | | // 角色映射 |
| | | const userTypeMap = { |
| | | 1: '游客', |
| | | 2: '营销员', |
| | | 3: '合伙人', |
| | | }; |
| | | |
| | | // 状态映射 |
| | | const statusMap = { |
| | | 1: { text: '使用中', color: 'success' }, |
| | |
| | | 2: { text: '营销员' }, |
| | | 3: { text: '合伙人' }, |
| | | }, |
| | | render: (text) => userTypeMap[text] || '-', |
| | | }, |
| | | { |
| | | title: '状态', |
| | |
| | | // import { getToken } from '@/utils/authority'; |
| | | import pathRegexp from 'path-to-regexp'; |
| | | import { parse } from 'querystring'; |
| | | import { uploadFile } from './service'; |
| | | /* eslint no-useless-escape:0 import/prefer-default-export:0 */ |
| | |
| | | document.body.removeChild(link); |
| | | }) |
| | | .catch((error) => console.error('Error downloading file:', error)); |
| | | }; |
| | | export const getAuthorityFromRouter = (router = [], pathname) => { |
| | | const authority = router.find( |
| | | ({ routes, path = '/' }) => |
| | | (path && pathRegexp(path).exec(pathname)) || |
| | | (routes && getAuthorityFromRouter(routes, pathname)), |
| | | ); |
| | | if (authority) return authority; |
| | | return undefined; |
| | | }; |
| | | export const getRouteAuthority = (path, routeData) => { |
| | | let authorities; |
| | | routeData.forEach((route) => { |
| | | // match prefix |
| | | if (pathRegexp(`${route.path}/(.*)`).test(`${path}/`)) { |
| | | if (route.authority) { |
| | | authorities = route.authority; |
| | | } // exact match |
| | | |
| | | if (route.path === path) { |
| | | authorities = route.authority || authorities; |
| | | } // get children authority recursively |
| | | |
| | | if (route.routes) { |
| | | authorities = getRouteAuthority(path, route.routes) || authorities; |
| | | } |
| | | } |
| | | }); |
| | | return authorities; |
| | | }; |
| | | |
| | | // export const upload2oss = (file) => { |