Compare commits

...

12 Commits

Author SHA1 Message Date
89d2bcdf29 feat: update api 2026-01-28 21:12:12 +08:00
ab9c034a72 feat: update api 2026-01-28 19:47:44 +08:00
17be93115d feat: 用接口 2026-01-27 21:09:40 +08:00
b142751b69 feat: update 2026-01-25 15:21:53 +08:00
aabb89b5f5 💥 feat(模块): 加群 2026-01-22 23:54:27 +08:00
bab3770337 💥 feat(模块): 加群 2026-01-22 20:59:26 +08:00
b83a91e2f2 💥 feat(模块): copy提示 2026-01-22 20:46:29 +08:00
7464906605 feat: 2026-01-22 20:26:27 +08:00
5a193bd585 💥 feat(模块): 添加了个很棒的功能 2026-01-18 15:53:43 +08:00
da323690db 💥 feat(模块): 添加了个很棒的功能 2026-01-18 09:35:45 +08:00
d294fb1306 💥 feat(模块): 添加了个很棒的功能 2026-01-18 09:26:59 +08:00
4e47f22df4 Update index.tsx 2026-01-04 15:07:09 +08:00
8 changed files with 146 additions and 61 deletions

View File

@ -27,6 +27,7 @@ export enum AdminAPI {
MATERIAL_UPDATE = '/api/bo/resume/update',
// 七牛
UPLOAD_FILE = '/api/backend/file/upload',
UPLOAD_FILE_WX = '/api/cityOperator/wxCp/uploadImg',
// 运营人员
STAFF_LIST = '/api/staff/getStaffPageList',
STAFF_ALL = '/api/staff/getAllStaff',
@ -42,4 +43,5 @@ export enum AdminAPI {
GIVE_VIP = '/api/bo/giveVip/give',
// 产品列表
PRODUCT_LIST = '/api/bo/product/listByType/{productType}',
GET_PRODUCT_SPEC = '/api/bo/product/getBySpecId/{productSpecId}'
}

View File

@ -5,9 +5,8 @@ import {
ProFormInstance,
ProFormSelect,
ProFormText,
ProFormDigit,
ProFormMoney,
ProFormUploadButton,
ProFormDependency,
} from '@ant-design/pro-components';
import { ModalForm, PageContainer, ProTable } from '@ant-design/pro-components';
import '@umijs/max';
@ -19,8 +18,14 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { CITY_OPTIONS } from '@/constants/city';
import { TIME_FORMAT } from '@/constants/global';
import { uploadFile } from '@/services/file';
import { deleteCityOperator, getAllStaffList, getCityOpratorList, updateCityOperator } from '@/services/list';
import { uploadFileWx } from '@/services/file';
import {
deleteCityOperator,
getAllStaffList,
getCityOpratorList,
getProductList,
updateCityOperator,
} from '@/services/list';
const useStyles = createStyles(({ token }) => {
return {
@ -37,8 +42,13 @@ const useStyles = createStyles(({ token }) => {
};
});
const calcPrice = (productSpecId: string, specList: API.ProductSpecListItem[]) => {
const product = specList.find(o => o.productSpecId === productSpecId);
return { price: product?.showPrice, originalPrice: product?.originalPrice, sendCount: product?.count };
};
const TableList: React.FC = () => {
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
const [specList, setSpecList] = useState<API.ProductSpecListItem[]>([]);
const [currentRow, setCurrentRow] = useState<API.CityOperatorListItem>();
const actionRef = useRef<ActionType>();
const formRef = useRef<ProFormInstance>();
@ -47,6 +57,14 @@ const TableList: React.FC = () => {
return staffOptions.find(it => it.isDefault) || staffOptions[0];
}, [staffOptions]);
const { styles } = useStyles();
const specOptions = useMemo(
() =>
specList.map(it => ({
value: it.productSpecId,
label: it.title,
})),
[specList],
);
const getAllStaffOptions = async () => {
try {
@ -63,17 +81,23 @@ const TableList: React.FC = () => {
}
};
const getAllSpectList = async () => {
const list = await getProductList('GROUP_BATCH_PUSH');
setSpecList(list);
};
const handleDelete = async (id: number) => {
await deleteCityOperator(id);
message.success('操作成功');
actionRef.current?.reload();
};
const handleUpload = async (file: File) => {
const { url } = await uploadFile({ file, type: 'IMAGE' });
const url = await uploadFileWx({ file, id: currentRow?.id });
return url;
};
useEffect(() => {
getAllStaffOptions();
getAllSpectList();
}, []);
const columns: ProColumns<API.CityOperatorListItem>[] = [
@ -121,7 +145,7 @@ const TableList: React.FC = () => {
copyable: true,
search: false,
render(_dom, { contactQrCode }) {
return <img className={styles.img} src={contactQrCode} alt="" />;
return contactQrCode ? <img className={styles.img} src={contactQrCode} alt="" /> : '-';
},
},
{
@ -131,21 +155,22 @@ const TableList: React.FC = () => {
copyable: true,
search: false,
render(_dom, { groupQrCode }) {
return <img className={styles.img} src={groupQrCode} alt="" />;
return groupQrCode ? <img className={styles.img} src={groupQrCode} alt="" /> : '-';
},
},
{
title: '可群发数量',
dataIndex: 'sendCount',
dataIndex: 'productSpecId',
valueType: 'textarea',
search: false,
renderText: productSpecId => calcPrice(productSpecId, specList).sendCount || '-',
},
{
title: '群代发价格',
dataIndex: 'price',
dataIndex: 'productSpecId',
valueType: 'textarea',
search: false,
renderText: cents => (cents ? (cents / 100).toFixed(0) : '-'),
renderText: productSpecId => calcPrice(productSpecId, specList).price || '-',
},
{
title: '操作',
@ -158,12 +183,24 @@ const TableList: React.FC = () => {
onClick={() => {
handleUpdateModalOpen(true);
setCurrentRow(record);
setTimeout(() => {
formRef.current?.setFieldsValue({
...record,
city: {
label: record.cityName,
value: record.cityCode,
},
qrCode: record.groupQrCode
? [
{
uid: `${record.id}_qrCode`,
name: `${record.cityName}_qrCode`,
status: 'done',
url: record.groupQrCode,
},
]
: undefined,
});
});
}}
>
@ -214,9 +251,8 @@ const TableList: React.FC = () => {
cityCode: formData.city.value,
cityName: formData.city.label,
groupLink: formData.groupLink,
sendCount: formData.sendCount,
qroupQrCode: formData.qrCode[0].xhr.responseURL,
price: formData.price,
productSpecId: formData.productSpecId,
groupQrCode: formData.qrCode[0].xhr ? formData.qrCode[0].xhr.responseURL : formData.qrCode[0].url,
};
console.log('update confirm', formData, params);
try {
@ -245,7 +281,7 @@ const TableList: React.FC = () => {
<ProFormText name="groupLink" label="进群链接" rules={[{ message: '请输入链接', type: 'url' }]} />
<ProFormUploadButton
name="qrCode"
label="上传"
label="进群二维码"
max={1}
accept="image/*"
rules={[{ required: true, message: '必填项' }]}
@ -254,29 +290,22 @@ const TableList: React.FC = () => {
}}
action={handleUpload}
/>
<ProFormDigit name="sendCount" label="可群发数量" min={1} fieldProps={{ precision: 0 }} />
<ProFormMoney
name="price"
label="价格"
min={0}
fieldProps={{
precision: 0,
formatter: value => {
if (!value) return '0';
return `${Math.floor(value / 100)}`;
},
parser: value => {
if (!value) return 0;
const numValue = parseInt(value.toString().replace(/[^\d]/g, ''));
return numValue * 100;
},
}}
transform={(value, namePath) => {
return {
[namePath]: value ? Math.round(Number(value)) : 0,
};
}}
<ProFormSelect
mode="single"
name="productSpecId"
label="可群发数量"
options={specOptions}
rules={[{ required: true, message: '必填项' }]}
/>
<ProFormDependency name={['productSpecId']}>
{({ productSpecId }) =>
productSpecId ? (
<div>
{calcPrice(productSpecId, specList).price}{calcPrice(productSpecId, specList).originalPrice}
</div>
) : null
}
</ProFormDependency>
</ModalForm>
</PageContainer>
);

View File

@ -144,7 +144,7 @@ const TableList: React.FC = () => {
handleUpdateModalOpen(false);
}}
>
<ProFormText name="imOpenId" label="用户ID" rules={[{ message: '请输入用户ID', required: true }]} />
<ProFormText name="userId" label="用户ID" rules={[{ message: '请输入用户ID', required: true }]} />
<ProFormRadio.Group
label="选择类型"
name="productType"

View File

@ -1,10 +1,12 @@
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
import { ModalForm, PageContainer, ProFormSelect, ProFormText, ProTable } from '@ant-design/pro-components';
import { message, Typography } from 'antd';
import '@umijs/max';
import { Select } from 'antd';
import dayjs from 'dayjs';
import React, { useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
const { Text } = Typography;
import { TIME_FORMAT } from '@/constants/global';
import { getPublisherList, updatePublisherInfo } from '@/services/list';
@ -35,12 +37,37 @@ const PRIORITY_OPTIONS = [
{ label: '正常处理', value: 2 },
];
const COPY_TIMES = '__copy_times__';
const COPY_EXP = '__copy_exp__';
const TableList: React.FC = () => {
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState<API.PublisherListItem>();
const actionRef = useRef<ActionType>();
const formRef = useRef<ProFormInstance>();
const handleCopy = useCallback(() => {
const storedTimes = localStorage.getItem(COPY_TIMES);
const copyExp = localStorage.getItem(COPY_EXP);
const now = Date.now();
let currentTimes = 0;
if (!copyExp || now >= parseInt(copyExp, 10)) {
currentTimes = 1;
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0, 0, 0, 0);
localStorage.setItem(COPY_EXP, tomorrow.getTime().toString());
} else {
currentTimes = parseInt(storedTimes || '0', 10) + 1;
}
localStorage.setItem(COPY_TIMES, `${currentTimes}`);
message.success(`今天已复制${currentTimes}`);
}, []);
const columns: ProColumns<API.PublisherListItem>[] = [
{
title: '发布人昵称',
@ -52,7 +79,17 @@ const TableList: React.FC = () => {
title: '发布人ID',
dataIndex: 'blPublisherId',
valueType: 'textarea',
copyable: true,
render(blPublisherId) {
return (
<Text
copyable={{
onCopy: handleCopy,
}}
>
{blPublisherId}
</Text>
);
},
},
{
title: '优先级',

View File

@ -95,6 +95,7 @@ const TableList: React.FC = () => {
onClick={() => {
handleUpdateModalOpen(true);
setCurrentRow(record);
setTimeout(() => {
formRef.current?.setFieldsValue({
...record,
qrCode: [
@ -106,6 +107,7 @@ const TableList: React.FC = () => {
},
],
});
});
}}
>
@ -155,7 +157,7 @@ const TableList: React.FC = () => {
id: currentRow?.id,
isDefault: currentRow?.isDefault || 0,
staffName: formData?.staffName,
staffQrCode: formData.qrCode[0].xhr.responseURL,
staffQrCode: formData.qrCode[0].xhr ? formData.qrCode[0].xhr.responseURL : formData.qrCode[0].url,
};
console.log('update confirm', formData, params);
try {

View File

@ -11,3 +11,13 @@ export async function uploadFile(options: { file: File; type: 'VIDEO' | 'IMAGE'
data: options,
});
}
export async function uploadFileWx(options: { file: File; id?: number }): Promise<string> {
return request<string>(AdminAPI.UPLOAD_FILE_WX, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
data: options,
});
}

View File

@ -327,3 +327,9 @@ export async function getProductList(productType: API.ProductType) {
method: 'GET',
});
}
export async function getProductSpec(productSpecId: API.ProductType) {
return request<API.ProductSpecListItem>(buildUrl(AdminAPI.GET_PRODUCT_SPEC, {productSpecId}), {
method: 'GET',
})
}

View File

@ -363,10 +363,9 @@ declare namespace API {
staffId: number;
cityCode: string;
groupLink: string;
sendCount: number;
productSpecId: string;
contactQrCode: string;
groupQrCode: string;
price: number;
id: number;
}
@ -377,9 +376,8 @@ declare namespace API {
cityName?: string;
cityCode: string;
groupLink: string;
qroupQrCode: string;
sendCount?: number;
price?: number;
groupQrCode: string;
productSpecId?: string;
created?: string;
updated?: string;
}
@ -411,6 +409,7 @@ declare namespace API {
productSpecId: string;
productType: ProductType;
productName: string;
count: number;
title: string;
priceText: string;
payPrice: number;