feat(ui): 新增请求方法及表格

This commit is contained in:
廖彦棋 2024-03-06 13:55:38 +08:00
parent 06fa54fd25
commit 6399d13a49
19 changed files with 667 additions and 90 deletions

View File

@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"clear": "rimraf node_modules projects/vue-admin/node_modules projects/vue-mobie/node_modules projects/vue-web/node_modules",
"clear": "rimraf node_modules projects/vue-admin/node_modules projects/vue-mobile/node_modules projects/vue-web/node_modules",
"dev": "pnpm --filter=@gpt-vue-projects/* run dev",
"build": "pnpm --filter=@gpt-vue-projects/* run build"
},

View File

@ -8,5 +8,9 @@
},
"keywords": [],
"author": "",
"license": "ISC"
"license": "ISC",
"dependencies": {
"axios": "^1.6.7",
"uuid": "^9.0.1"
}
}

View File

@ -0,0 +1,28 @@
import axios from "axios";
import tokenHandler from "./token";
const { _tokenData, refreshToken, setCurRequest } = tokenHandler();
const createInstance = (baseURL: string = (import.meta as any).env.VITE_PROXY_BASE_URL) => {
const instance = axios.create({
baseURL,
timeout: 10000,
withCredentials: true,
});
instance.interceptors.request.use((config) => {
if (config.url !== _tokenData.get("lastRequest")) {
refreshToken();
}
if (config.method === "post") {
setCurRequest(config.url);
config.headers["request-id"] = _tokenData.get("__token");
}
return config;
});
return instance;
}
export default createInstance;

View File

@ -0,0 +1,13 @@
import { getUUID } from "../utils";
const _tokenData = new Map();
export default function tokenHandler() {
const refreshToken = () => {
_tokenData.set("__token", getUUID());
_tokenData.set("lastRequest", null);
};
const setCurRequest = (curRequest?: string) => {
_tokenData.set("lastRequest", curRequest);
};
return { _tokenData, refreshToken, setCurRequest };
}

14
gpt-vue/packages/type.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
export interface BaseResponse<T> {
code: number;
data?: T;
message?: string;
}
export interface ListResponse<T = Record<string, unknown>> {
items: T[];
page: number;
page_size: number;
total: number;
total_page: number
}

View File

@ -0,0 +1,5 @@
import { v4 as uuidV4 } from "uuid";
export const getUUID = () => {
return uuidV4();
};

View File

@ -12,13 +12,24 @@ importers:
specifier: ^5.0.5
version: 5.0.5
packages: {}
packages:
dependencies:
axios:
specifier: ^1.6.7
version: 1.6.7
uuid:
specifier: ^9.0.1
version: 9.0.1
projects/vue-admin:
dependencies:
'@arco-design/web-vue':
specifier: ^2.54.6
version: 2.54.6(vue@3.4.21)
'@gpt-vue/packages':
specifier: workspace:^1.0.0
version: link:../../packages
pinia:
specifier: ^2.1.7
version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
@ -72,58 +83,6 @@ importers:
specifier: ^1.8.27
version: 1.8.27(typescript@5.3.3)
projects/vue-mobie:
dependencies:
pinia:
specifier: ^2.1.7
version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
vue:
specifier: ^3.4.15
version: 3.4.21(typescript@5.3.3)
vue-router:
specifier: ^4.2.5
version: 4.3.0(vue@3.4.21)
devDependencies:
'@rushstack/eslint-patch':
specifier: ^1.3.3
version: 1.7.2
'@tsconfig/node20':
specifier: ^20.1.2
version: 20.1.2
'@types/node':
specifier: ^20.11.10
version: 20.11.24
'@vitejs/plugin-vue':
specifier: ^5.0.3
version: 5.0.4(vite@5.1.5)(vue@3.4.21)
'@vitejs/plugin-vue-jsx':
specifier: ^3.1.0
version: 3.1.0(vite@5.1.5)(vue@3.4.21)
'@vue/eslint-config-typescript':
specifier: ^12.0.0
version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
'@vue/tsconfig':
specifier: ^0.5.1
version: 0.5.1
eslint:
specifier: ^8.49.0
version: 8.57.0
eslint-plugin-vue:
specifier: ^9.17.0
version: 9.22.0(eslint@8.57.0)
npm-run-all2:
specifier: ^6.1.1
version: 6.1.2
typescript:
specifier: ~5.3.0
version: 5.3.3
vite:
specifier: ^5.0.11
version: 5.1.5(@types/node@20.11.24)
vue-tsc:
specifier: ^1.8.27
version: 1.8.27(typescript@5.3.3)
projects/vue-mobile:
dependencies:
pinia:
@ -1413,6 +1372,20 @@ packages:
engines: {node: '>=8'}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/axios@1.6.7:
resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==}
dependencies:
follow-redirects: 1.15.5
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/b-tween@0.3.3:
resolution: {integrity: sha512-oEHegcRpA7fAuc9KC4nktucuZn2aS8htymCPcP3qkEGPqiBH+GfqtqoG2l7LxHngg6O0HFM7hOeOYExl1Oz4ZA==}
dev: false
@ -1523,6 +1496,13 @@ packages:
color-string: 1.9.1
dev: false
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/compute-scroll-into-view@1.0.20:
resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
dev: false
@ -1587,6 +1567,11 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -1854,6 +1839,16 @@ packages:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
dev: true
/follow-redirects@1.15.5:
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'}
@ -1862,6 +1857,15 @@ packages:
signal-exit: 4.1.0
dev: true
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
@ -2203,6 +2207,18 @@ packages:
picomatch: 2.3.1
dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@ -2426,6 +2442,10 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
requiresBuild: true
@ -2713,41 +2733,10 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/vite@5.1.5(@types/node@20.11.24):
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
engines: {node: ^18.0.0 || >=20.0.0}
/uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': 20.11.24
esbuild: 0.19.12
postcss: 8.4.35
rollup: 4.12.0
optionalDependencies:
fsevents: 2.3.3
dev: true
dev: false
/vite@5.1.5(@types/node@20.11.24)(less@4.2.0):
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}

View File

@ -0,0 +1,3 @@
VITE_PROXY_BASE_URL="/api"
VITE_TARGET_URL="http://172.22.11.2:5678"
VITE_SOCKET_IO_URL="http://172.28.1.3:8899"

View File

@ -0,0 +1,3 @@
VITE_PROXY_BASE_URL=""
VITE_TARGET_URL="/"
VITE_SOCKET_IO_URL="/"

View File

@ -13,6 +13,7 @@
},
"dependencies": {
"@arco-design/web-vue": "^2.54.6",
"@gpt-vue/packages": "workspace:^1.0.0",
"pinia": "^2.1.7",
"vue": "^3.4.15",
"vue-router": "^4.2.5"

View File

@ -0,0 +1,117 @@
<script lang="ts" setup>
import { computed, ref, type PropType } from "vue";
import { getDefaultFormData, useComponentConfig } from "./utils";
import { ValueType } from "./type.d";
import type { SearchTableColumns, SearchColumns } from "./type";
const props = defineProps({
modelValue: {
type: Object,
default: () => ({}),
},
columns: {
type: Array as PropType<SearchTableColumns[]>,
default: () => [],
},
submitting: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["update:modelValue", "request"]);
const size = "small";
const collapsed = ref(false);
const formData = computed({
get: () => props.modelValue,
set(value) {
emits("update:modelValue", value);
},
});
const searchColumns = computed(() => {
return props.columns?.filter(
(item) => item.dataIndex && item.search
) as (SearchColumns & { dataIndex: string })[];
});
const optionsEvent = {
onReset: () => {
formData.value = getDefaultFormData(props.columns);
emits("request");
},
onSearch: () => emits("request"),
onCollapse: (value: boolean) => {
collapsed.value = value ?? !collapsed.value;
},
};
</script>
<template>
<AForm
v-if="searchColumns?.length"
class="search-form-conteiner"
:model="formData"
:size="size"
:label-col-props="{ span: 0 }"
:wrapper-col-props="{ span: 24 }"
@submit="optionsEvent.onSearch"
>
<AGrid
:cols="{ md: 1, lg: 2, xl: 3, xxl: 5 }"
:row-gap="12"
:col-gap="12"
:collapsed="collapsed"
>
<AGridItem
v-for="item in searchColumns"
:key="item.dataIndex"
style="transition: all 0.3s ease-in-out"
>
<AFormItem :field="item.dataIndex" :label="(item.title as string)">
<slot :name="item.search.slotsName">
<component
v-model="formData[item.dataIndex]"
:is="
ValueType[item.search.valueType ?? 'input'] ??
item.search.render
"
v-bind="useComponentConfig(size, item)"
/>
</slot>
</AFormItem>
</AGridItem>
<AGridItem suffix>
<ASpace>
<slot name="search-options" :option="optionsEvent">
<AButton
type="primary"
html-type="submit"
:size="size"
:loading="submitting"
>
<icon-search />
<span>查询</span>
</AButton>
<AButton
:size="size"
@click="optionsEvent.onReset"
:loading="submitting"
>
<icon-refresh />
<span>重置</span>
</AButton>
</slot>
<slot name="search-extra" />
</ASpace>
</AGridItem>
</AGrid>
</AForm>
</template>
<style scoped>
.search-form-conteiner {
padding: 16px 0;
}
</style>

View File

@ -0,0 +1,87 @@
<script lang="ts" setup>
import { computed, ref, onActivated } from "vue";
import useAsyncTable from "./useAsyncTable";
import FormSection from "./FormSection.vue";
import type { SearchTableProps } from "./type";
import { useTableScroll, getDefaultFormData, useRequestParams } from "./utils";
import { Message } from "@arco-design/web-vue";
const props = defineProps<SearchTableProps>();
const formData = ref({ ...getDefaultFormData(props.columns) });
const tableContainerRef = ref<HTMLElement>();
//
const requestParams = computed(() => ({
...useRequestParams(props.columns, formData.value),
...props.params,
}));
const [tableConfig, getList] = useAsyncTable(props.request, requestParams);
const _columns = computed(() => {
return props.columns.map((item) => ({
ellipsis: true,
tooltip: true,
...item,
}));
});
const handleSearch = async (tips?: boolean) => {
tips && Message.success("操作成功");
await getList();
};
onActivated(handleSearch);
</script>
<template>
<div class="search-table">
<div class="search-table-header">
<div>
<slot name="header-title">{{ props.headerTitle }}</slot>
</div>
<div class="header-option">
<slot
name="header-option"
:formData="formData"
:reload="handleSearch"
/>
</div>
</div>
<FormSection
v-model="formData"
:columns="columns"
:submitting="(tableConfig.loading as boolean)"
@request="handleSearch"
/>
<div ref="tableContainerRef" class="search-table-container">
<ATable
v-bind="{
...$attrs,
...tableConfig,
...props,
scroll: useTableScroll(_columns, tableContainerRef as HTMLElement),
columns: _columns
}"
>
<template v-for="slot in Object.keys($slots)" #[slot]="config">
<slot :name="slot" v-bind="{ ...config, reload: handleSearch }" />
</template>
</ATable>
</div>
</div>
</template>
<style scoped>
.search-table {
display: flex;
flex-direction: column;
height: 100%;
}
.search-table-container {
flex: 1;
}
.search-table-header {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,49 @@
import type { Component } from "vue";
import type { JsxElement } from "typescript";
import {
DatePicker,
Input,
InputNumber,
RadioGroup,
RangePicker,
Select,
Switch,
type TableColumnData,
} from "@arco-design/web-vue";
import type { TableOriginalProps, TableRequest } from "./useAsyncTable";
type Object = Record<string, unknown>;
export enum ValueType {
"input" = Input,
"select" = Select,
"number" = InputNumber,
"date" = DatePicker,
"range" = RangePicker,
"radio" = RadioGroup,
"switch" = Switch,
}
export type SearchConfig = {
valueType?: keyof typeof ValueType;
fieldProps?: Object;
render?: Component | JsxElement;
slotsName?: string;
defaultValue?: any;
transform?: (value) => Record<string, any>;
};
export interface SearchTableColumns extends TableColumnData {
search?: SearchConfig;
hideInTable?: boolean;
[key: string]: any;
}
export type SearchColumns = SearchTableColumns & { search: SearchConfig };
export interface SearchTableProps extends /* @vue-ignore */ TableOriginalProps {
request: TableRequest<Object>;
params?: Object;
columns: SearchTableColumns[];
headerTitle?: string;
}

View File

@ -0,0 +1,63 @@
import { computed, onMounted, reactive, unref, type Ref } from "vue";
import type { TableInstance } from "@arco-design/web-vue";
import type { BaseResponse, ListResponse } from "@gpt-vue/packages/type";
export type TableOriginalProps = TableInstance["$props"];
export type TableRequest<T extends Record<string, unknown>> = (params?: any) => Promise<BaseResponse<ListResponse<T>>>
export type TableReturn = [TableOriginalProps, () => Promise<void>];
function useAsyncTable<T extends Record<string, unknown>>(
request: TableRequest<T>,
params?: Ref<Record<string, unknown>>
): TableReturn {
const paginationState = reactive({
current: 1,
pageSize: 10,
total: 0,
});
const tableState = reactive({
loading: false,
data: []
})
const tableConfig = computed<TableOriginalProps>(() => {
return {
...tableState,
rowKey: "id",
pagination: {
...paginationState,
showTotal: true,
showPageSize: true,
},
onPageChange: (page) => {
paginationState.current = page;
getTableData();
},
onPageSizeChange(pageSize) {
paginationState.pageSize = pageSize;
getTableData();
},
};
});
const getTableData = async () => {
tableState.loading = true
try {
const { data } = await request({
...unref(params ?? {}),
page: paginationState.current,
pageSize: paginationState.pageSize,
});
tableState.data = data?.items;
paginationState.total = data.total;
} finally {
tableState.loading = false
}
};
onMounted(getTableData);
return [tableConfig, getTableData] as TableReturn;
}
export default useAsyncTable;

View File

@ -0,0 +1,53 @@
import type { TableColumnData } from "@arco-design/web-vue";
import type { SearchTableColumns, SearchColumns } from "./type";
export function useTableXScroll(columns: TableColumnData[]) {
return columns.reduce((prev, curr) => {
const width = curr.width ?? 150;
return prev + width;
}, 0);
}
export function useTableScroll(columns: SearchTableColumns[], container?: HTMLElement) {
const x = columns.reduce((prev, curr) => {
const width = curr.hideInTable ? 0 : curr.width ?? 150;
return prev + width;
}, 0);
const y = container?.clientHeight ?? undefined;
return { x, y };
}
export function getDefaultFormData(columns: SearchTableColumns[]) {
return columns?.reduce((field, curr) => {
if (curr.dataIndex && curr?.search?.defaultValue) {
field[curr.dataIndex] = curr.search.defaultValue;
}
return field;
}, {});
}
export function useRequestParams(
columns: SearchTableColumns[],
originFormData: Record<string, any>
) {
const filterFormData = columns?.reduce((prev, curr) => {
if (!curr.dataIndex || !curr.search) {
return prev;
}
if (curr?.search?.transform) {
const filters = curr.search.transform(originFormData[curr.dataIndex]);
return Object.assign(prev, filters);
}
return Object.assign(prev, { [curr.dataIndex]: originFormData[curr.dataIndex] });
}, {});
return filterFormData as Record<string, any>;
}
export function useComponentConfig(size: string, item: SearchColumns) {
return {
size,
placeholder: item.search.valueType === "range" ? ["开始时间", "结束时间"] : item.title,
allowClear: true,
...(item.search.fieldProps ?? {}),
};
}

View File

@ -0,0 +1,57 @@
import { Notification } from "@arco-design/web-vue";
import createInstance from "@gpt-vue/packages/request"
import type { BaseResponse } from "@gpt-vue/packages/type";
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/common/upload/minio";
export const instance = createInstance()
instance.interceptors.request.use((config) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data }: { data: BaseResponse<unknown> } = response
if (data && typeof data === "object" && data.code !== 0) {
Notification.error(data.message ?? '未知错误')
}
return { data, response } as any;
},
(error) => {
const STATUS_CODE: any = {
401: {
msg: error.response.data || "没有操作权限!",
event: null,
},
500: {
msg: error.response.data || "系统正在部署升级中,请稍后再试!",
event: null,
},
};
const statusCodeEvent = STATUS_CODE?.[error.response.status];
if (statusCodeEvent) {
Notification.error(statusCodeEvent.msg);
statusCodeEvent.event?.();
}
if (error.message.indexOf("timeout") !== -1) {
Notification.error("连接超时");
}
return Promise.reject(error);
}
);
function http<T = any>(config: any): Promise<BaseResponse<T>> {
return instance(config).then((res) => {
return res.data;
}) as unknown as Promise<BaseResponse<T>>;
}
export function originHttp<T = any>(config: any) {
return instance<T>(config as any).then((res) => res);
}
export default http;

View File

@ -1,6 +1,7 @@
import {
IconUser,
IconDashboard
IconDashboard,
IconOrderedList,
} from "@arco-design/web-vue/es/icon";
const menu = [
@ -22,6 +23,15 @@ const menu = [
},
component: () => import('@/views/User/UserContainer.vue')
},
{
path: '/order',
name: 'Order',
meta: {
title: "充值订单",
icon: IconOrderedList,
},
component: () => import('@/views/Order/OrderContainer.vue')
},
];
export default menu;

View File

@ -0,0 +1,72 @@
<script lang="ts" setup>
import SearchTable from "@/components/SearchTable/SearchTable.vue";
import type { SearchTableColumns } from "@/components/SearchTable/type";
import { getList } from "./api";
const columns: SearchTableColumns[] = [
{
dataIndex: "order_no",
title: "订单号",
search: {
valueType: "input",
},
},
{
dataIndex: "username",
title: "下单用户",
},
{
dataIndex: "subject",
title: "产品名称",
},
{
dataIndex: "amount",
title: "订单金额",
},
{
dataIndex: "remark.calls",
title: "调用次数",
},
{
dataIndex: "created_at",
title: "下单时间",
},
{
dataIndex: "status",
title: "订单状态",
hideInTable: true,
search: {
valueType: "select",
fieldProps: {
options: [
{ label: "全部", value: -1 },
{ label: "未支付", value: 0 },
{ label: "已支付", value: 2 },
],
},
},
},
{
dataIndex: "pay_time",
title: "支付时间",
search: {
valueType: "range",
},
},
{
dataIndex: "pay_way",
title: "支付方式",
},
{
title: "操作",
slotName: "actions",
},
];
</script>
<template>
<SearchTable :request="getList" :columns="columns">
<template #actions="{ record }">
<a-link :key="record.id" status="danger">删除</a-link>
</template>
</SearchTable>
</template>

View File

@ -0,0 +1,9 @@
import http from "@/http/config";
export const getList = (params?: Record<string, unknown>) => {
return http({
url: "/admin/order/list",
methods: "get",
params
})
}