Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 045e491ade | |||
| 64fdaba93d | |||
| 50d5f54015 | |||
| a77592dec6 | |||
| 00867ff960 | |||
| 4c08bd1f8c | |||
| ab6f5c787e | |||
| 79a7ea8c56 | |||
| b5ba0ca3fb | |||
| 9b5e2807b5 | |||
| 3a30e81183 | |||
| 28386f5429 | |||
| b0b8689acb | |||
| 8012cb82fc | |||
| 6f8e5036f7 |
Generated
+1721
File diff suppressed because it is too large
Load Diff
@@ -83,13 +83,37 @@
|
||||
}
|
||||
|
||||
.divisionList {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.coverWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
|
||||
:global(.ant-image) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.divisionItem {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
padding: 4px 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #1677ff;
|
||||
background: #e6f4ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,11 +270,13 @@ export default defineComponent({
|
||||
{/* ===== 封面 ===== */}
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>封面</div>
|
||||
<Image
|
||||
style={{ maxHeight: '380px' }}
|
||||
src={detail.value.img || defaultCover}
|
||||
fallback={defaultCover}
|
||||
/>
|
||||
<div class={styles.coverWrapper}>
|
||||
<Image
|
||||
style={{ maxHeight: '380px' }}
|
||||
src={detail.value.img || defaultCover}
|
||||
fallback={defaultCover}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 赛事公告 ===== */}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f0f0f0;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.imageGrid {
|
||||
|
||||
@@ -89,7 +89,7 @@ export default defineComponent({
|
||||
const renderAction = (record: any) => {
|
||||
const status = record.status;
|
||||
|
||||
if (status === '0' || status === '1') {
|
||||
if (['0', '1', '2'].includes(status)) {
|
||||
return (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => openDetailModal(record)}>
|
||||
|
||||
@@ -180,7 +180,7 @@ export default defineComponent({
|
||||
},
|
||||
{
|
||||
key: 'totalBalance',
|
||||
label: '平台余额(所有用户余额总和)',
|
||||
label: '平台余额(所有用户余额总和 = 总收入 - 提现金额)',
|
||||
value: s.totalBalance,
|
||||
bgColor: '#e6f4ff',
|
||||
accentColor: '#1677ff',
|
||||
@@ -224,7 +224,11 @@ export default defineComponent({
|
||||
<div class={styles.statValue} style={{ color: item.accentColor }}>
|
||||
¥{formatMoney(item.value)}
|
||||
</div>
|
||||
{/* 趋势文字暂时隐藏 */}
|
||||
{/* {item.key === 'totalBalance' && (
|
||||
<div class={styles.statDesc} style={{ color: 'rgba(0, 0, 0, 0.45)' }}>
|
||||
= 总收入 - 提现金额
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,9 @@ export function useReportsModel() {
|
||||
// ===== 图表数据 =====
|
||||
const chartData = computed(() => {
|
||||
const list = data.value?.monthList || [];
|
||||
return list.map((item) => ({
|
||||
// 按月份升序排列,确保图表 X 轴时间顺序正确
|
||||
const sorted = [...list].sort((a, b) => a.month.localeCompare(b.month));
|
||||
return sorted.map((item) => ({
|
||||
month: dayjs(item.month).format('MM月'),
|
||||
order: toNumber(item.orderAmount),
|
||||
refund: toNumber(item.refundAmount),
|
||||
|
||||
@@ -160,7 +160,8 @@ export default defineComponent({
|
||||
placeholder="请输入"
|
||||
style={{ width: '140px' }}
|
||||
value={filterForm.minBalance || undefined}
|
||||
precision={0}
|
||||
precision={2}
|
||||
step={0.01}
|
||||
min={0}
|
||||
controls={false}
|
||||
onUpdate:value={(val: any) =>
|
||||
|
||||
@@ -143,9 +143,14 @@
|
||||
}
|
||||
|
||||
// ===== 上传 =====
|
||||
.uploader {
|
||||
.uploadWrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.uploader {
|
||||
:global {
|
||||
.ant-upload-list-picture-card-container {
|
||||
width: 96px;
|
||||
@@ -159,6 +164,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pasteTip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
background: #e6f7ff;
|
||||
border-radius: 4px;
|
||||
color: #1677ff;
|
||||
font-size: 12px;
|
||||
|
||||
span {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadTrigger {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import { defineComponent, reactive, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useEffect } from '@/hooks';
|
||||
import { Modal, Radio, Input, Button, Upload, Image, Spin, message } from 'ant-design-vue';
|
||||
import type { UploadFile } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { PlusOutlined, CopyOutlined } from '@ant-design/icons-vue';
|
||||
import { uploadFile, OssUploadType } from '@/utils/oss';
|
||||
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
|
||||
import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel';
|
||||
@@ -41,6 +41,34 @@ export default defineComponent({
|
||||
const auditForm = reactive({ pass: true, remark: '' });
|
||||
const imageList = ref<UploadFile[]>([]);
|
||||
const submitting = ref(false);
|
||||
const uploadAreaRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// ===== 粘贴图片上传 =====
|
||||
const handlePaste = async (e: ClipboardEvent) => {
|
||||
if (!props.visible || !uploadAreaRef.value) return;
|
||||
|
||||
const items = e.clipboardData?.items;
|
||||
if (!items) return;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item.type.indexOf('image') !== -1) {
|
||||
e.preventDefault();
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
await beforeUpload(file as unknown as UploadFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('paste', handlePaste);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('paste', handlePaste);
|
||||
});
|
||||
|
||||
// ===== 拉取详情 =====
|
||||
const fetchDetail = async (id: number) => {
|
||||
@@ -239,21 +267,27 @@ export default defineComponent({
|
||||
{auditForm.pass && <span class={styles.required}>*</span>}
|
||||
审核图片
|
||||
</span>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
fileList={imageList.value}
|
||||
beforeUpload={beforeUpload}
|
||||
onRemove={handleRemove}
|
||||
accept="image/*"
|
||||
class={styles.uploader}
|
||||
>
|
||||
{imageList.value.length >= MAX_IMAGE_COUNT ? null : (
|
||||
<div class={styles.uploadTrigger}>
|
||||
<PlusOutlined />
|
||||
<div class={styles.uploadText}>请选择图片</div>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
<div ref={uploadAreaRef} class={styles.uploadWrapper}>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
fileList={imageList.value}
|
||||
beforeUpload={beforeUpload}
|
||||
onRemove={handleRemove}
|
||||
accept="image/*"
|
||||
class={styles.uploader}
|
||||
>
|
||||
{imageList.value.length >= MAX_IMAGE_COUNT ? null : (
|
||||
<div class={styles.uploadTrigger}>
|
||||
<PlusOutlined />
|
||||
<div class={styles.uploadText}>选择或粘贴图片</div>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
<div class={styles.pasteTip}>
|
||||
<CopyOutlined />
|
||||
<span>支持 Ctrl+V 粘贴图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class={styles.footer}>
|
||||
<Button type="primary" onClick={handleSubmit} loading={submitting.value}>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
.cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
.view {
|
||||
flex-shrink: 0;
|
||||
color: #1677ff;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,81 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, DatePicker, Form, Space, Select, Pagination } from 'ant-design-vue';
|
||||
import { defineComponent, ref, nextTick } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
DatePicker,
|
||||
Form,
|
||||
Space,
|
||||
Select,
|
||||
Pagination,
|
||||
Modal,
|
||||
} from 'ant-design-vue';
|
||||
import { useLogModel, ACTION_TYPE_OPTIONS, ACTION_TYPE_MAP } from './model/useLogModel';
|
||||
import { useContainerSize } from '@/hooks';
|
||||
import { useContainerSize, useState, useEffect } from '@/hooks';
|
||||
import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||
import cellStyles from './index.module.less';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
/**
|
||||
* 操作内容单元格:最多 2 行截断,溢出时显示"查看"按钮
|
||||
*/
|
||||
const LogContentCell = defineComponent({
|
||||
name: 'LogContentCell',
|
||||
props: { text: { type: String, required: true } },
|
||||
setup(props) {
|
||||
const textRef = ref<HTMLElement | null>(null);
|
||||
const [overflow, setOverflow] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const checkOverflow = () => {
|
||||
const el = textRef.value;
|
||||
if (el) setOverflow(el.scrollHeight > el.clientHeight);
|
||||
};
|
||||
|
||||
let observer: ResizeObserver | null = null;
|
||||
useEffect(() => {
|
||||
if (!textRef.value) return;
|
||||
nextTick(checkOverflow);
|
||||
observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(textRef.value);
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
};
|
||||
}, [() => textRef.value]);
|
||||
|
||||
return () => {
|
||||
const raw = props.text;
|
||||
return (
|
||||
<div class={cellStyles.cell}>
|
||||
<div ref={textRef} class={cellStyles.text}>
|
||||
{raw}
|
||||
</div>
|
||||
{overflow.value ? (
|
||||
<span class={cellStyles.view} onClick={() => setModalOpen(true)}>
|
||||
查看
|
||||
</span>
|
||||
) : null}
|
||||
<Modal
|
||||
visible={modalOpen.value}
|
||||
title="操作内容"
|
||||
footer={null}
|
||||
width={600}
|
||||
centered
|
||||
destroyOnClose
|
||||
onCancel={() => setModalOpen(false)}
|
||||
>
|
||||
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.8 }}>
|
||||
{raw}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* bodyCell 渲染
|
||||
*/
|
||||
@@ -13,6 +83,10 @@ function renderBodyCell({ column, text }: { column: any; text: any }) {
|
||||
if (column.key === 'type') {
|
||||
return <span>{ACTION_TYPE_MAP[text as number] || text || '--'}</span>;
|
||||
}
|
||||
if (column.dataIndex === 'content') {
|
||||
const raw = text || '';
|
||||
return raw ? <LogContentCell text={raw} /> : <span>--</span>;
|
||||
}
|
||||
return <span>{text || '--'}</span>;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export function useLogModel() {
|
||||
const columns = [
|
||||
{ title: '操作类型', dataIndex: 'type', key: 'type', width: 140 },
|
||||
{ title: '操作人', dataIndex: 'nickname', key: 'nickname', width: 140 },
|
||||
{ title: '操作内容', dataIndex: 'content', key: 'content', minWidth: 360 },
|
||||
{ title: '操作内容', dataIndex: 'content', key: 'content', width: 480 },
|
||||
{ title: '操作时间', dataIndex: 'createDate', key: 'createDate', width: 180 },
|
||||
];
|
||||
|
||||
|
||||
@@ -104,13 +104,13 @@ export default defineComponent({
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="参数值" name="paramValue" rules={paramValueRules}>
|
||||
<Input
|
||||
<TextArea
|
||||
value={formData.paramValue}
|
||||
onUpdate:value={(val: any) => {
|
||||
formData.paramValue = val?.target?.value ?? val ?? '';
|
||||
}}
|
||||
placeholder="请输入参数值"
|
||||
allowClear
|
||||
rows={2}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -123,7 +123,7 @@ export default defineComponent({
|
||||
placeholder="请输入备注说明(选填,最多200字)"
|
||||
maxlength={200}
|
||||
showCount
|
||||
rows={3}
|
||||
rows={4}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@@ -8,11 +8,14 @@ import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||
import cellStyles from './index.module.less';
|
||||
|
||||
/**
|
||||
* 参数值单元格:最多 2 行截断,溢出时显示"查看"按钮
|
||||
* 文本单元格:最多 2 行截断,溢出时显示"查看"按钮
|
||||
*/
|
||||
const ParamValueCell = defineComponent({
|
||||
name: 'ParamValueCell',
|
||||
props: { text: { type: String, required: true } },
|
||||
const TruncatedTextCell = defineComponent({
|
||||
name: 'TruncatedTextCell',
|
||||
props: {
|
||||
text: { type: String, required: true },
|
||||
modalTitle: { type: String, required: true },
|
||||
},
|
||||
setup(props) {
|
||||
const textRef = ref<HTMLElement | null>(null);
|
||||
const [overflow, setOverflow] = useState(false);
|
||||
@@ -49,7 +52,7 @@ const ParamValueCell = defineComponent({
|
||||
) : null}
|
||||
<Modal
|
||||
visible={modalOpen.value}
|
||||
title="参数值"
|
||||
title={props.modalTitle}
|
||||
footer={null}
|
||||
width={600}
|
||||
centered
|
||||
@@ -90,14 +93,11 @@ function renderBodyCell({
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
// 参数值列:两行截断 + 查看弹窗
|
||||
if (column.dataIndex === 'paramValue') {
|
||||
// 参数值 / 备注列:两行截断 + 查看弹窗
|
||||
if (column.dataIndex === 'paramValue' || column.dataIndex === 'remark') {
|
||||
const raw = text || '';
|
||||
return raw ? <ParamValueCell text={raw} /> : <span>--</span>;
|
||||
}
|
||||
// 备注列:空值显示 --
|
||||
if (column.dataIndex === 'remark' && !text) {
|
||||
return <span>--</span>;
|
||||
const modalTitle = column.dataIndex === 'paramValue' ? '参数值' : '备注说明';
|
||||
return raw ? <TruncatedTextCell text={raw} modalTitle={modalTitle} /> : <span>--</span>;
|
||||
}
|
||||
return <span>{text ?? '--'}</span>;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function useParamsColumns() {
|
||||
title: '备注说明',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
minWidth: 240,
|
||||
width: 240,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import areaData from 'china-area-data';
|
||||
import areaData from 'china-area-data/data.json';
|
||||
|
||||
export interface CascadeOption {
|
||||
value: string;
|
||||
|
||||
Reference in New Issue
Block a user