1. 更新框架
This commit is contained in:
李琦
2026-08-10 17:56:36 +08:00
parent 82b10bd122
commit 3547c53f89
7 changed files with 4436 additions and 3985 deletions

View File

@@ -53,6 +53,15 @@
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/sortablejs": "catalog:"
"@types/sortablejs": "catalog:",
"@fortawesome/fontawesome-free": "^6.7.2",
"@vueup/vue-quill": "^1.2.0",
"axios": "^1.10.0",
"html2canvas": "^1.4.1",
"js-base64": "^3.7.7",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.487.0",
"md-editor-v3": "^5.4.5",
"xgplayer": "^3.0.24"
}
}

View File

@@ -3,38 +3,9 @@
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
/* eslint-disable vue/one-component-per-file */
// import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
notification,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
Textarea,
TimePicker,
TreeSelect,
Upload,
import type {
AutoCompleteProps,
ButtonProps,
CascaderProps,
@@ -60,7 +31,7 @@ import {
} from 'ant-design-vue';
import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
import type { Component, Ref, SetupContext } from 'vue';
import type { Component, Ref } from 'vue';
import type {
ApiComponentSharedProps,
@@ -84,8 +55,6 @@ import {
watch,
} from 'vue';
import { registerComponent } from '#/components/form/component-map';
import {
ApiComponent,
globalShareState,
@@ -109,6 +78,13 @@ type AdapterUploadProps = UploadProps & {
onHandleChange?: (event: UploadChangeParam) => void;
};
// ============================================================
// 所有 antd 组件统一用 defineAsyncComponent 异步加载
// ⚠️ 不要再从 'ant-design-vue' 同步 import 这些组件!
// 否则会和下面的 const 声明冲突,导致
// "Identifier 'Xxx' has already been declared"
// ============================================================
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
@@ -162,13 +138,17 @@ const PreviewGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),
);
// ============================================================
// withDefaultPlaceholder给组件注入 i18n placeholder + ref 透传
// ============================================================
const withDefaultPlaceholder = <T extends Component>(
component: Component,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: component.name,
name: (component as any).name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
@@ -351,7 +331,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
let isUnmounted = false;
let objectUrl: null | string = null;
const open = ref<boolean>(true);
const open = ref(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
const closeModal = () => {
@@ -425,7 +405,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
},
() =>
h(VCropper, {
ref: (ref: any) => (cropperRef.value = ref),
ref: (refValue: any) => (cropperRef.value = refValue),
img: objectUrl as string,
aspectRatio,
}),
@@ -443,7 +423,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
*/
const withPreviewUpload = () => {
return defineComponent({
name: Upload.name,
name: 'Upload',
emits: ['update:modelValue'],
setup(
props: any,
@@ -535,7 +515,7 @@ const withPreviewUpload = () => {
[data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
[data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
`;
document.head.append(style);
document.head?.append(style);
}
}
@@ -552,7 +532,7 @@ const withPreviewUpload = () => {
const container = document.querySelector(
`[data-upload-id="${uploadId}"] .ant-upload-list`,
) as HTMLElement;
) as HTMLElement | null;
if (!container) {
if (retryCount < 5) {
@@ -567,7 +547,7 @@ const withPreviewUpload = () => {
delayOnTouchOnly: true,
filter:
'.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
onEnd: (evt) => {
onEnd: (evt: { oldIndex?: number; newIndex?: number }) => {
const { oldIndex, newIndex } = evt;
if (
oldIndex === undefined ||
@@ -640,7 +620,6 @@ export type ComponentType =
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'GalleryPickLink' // OA 场景表单的图片/文件素材选择(透传 acceptTypes 过滤类型)
| 'IconPicker'
| 'Input'
| 'InputNumber'
@@ -658,10 +637,6 @@ export type ComponentType =
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| 'UploadImage'
| 'UploadImageSortable'
| 'UploadOssFile'
| 'UploadDraggerPaste'
| BaseFormComponentType;
/**
@@ -730,7 +705,7 @@ async function initComponentAdapter() {
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
DefaultButton: (props: any, { attrs, slots }: any) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
@@ -746,7 +721,7 @@ async function initComponentAdapter() {
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
PrimaryButton: (props: any, { attrs, slots }: any) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
@@ -762,8 +737,6 @@ async function initComponentAdapter() {
Upload: withPreviewUpload(),
};
// 自动注册自定义组件
registerComponent(components);
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);

View File

@@ -2,7 +2,6 @@
import type { NotificationItem } from '@vben/layouts';
import {computed, onUnmounted, ref, watch} from 'vue';
import { useRouter } from 'vue-router';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
@@ -38,6 +37,7 @@ import { useRouter } from 'vue-router';
import {getNoticeListApi, readAllApi} from "#/views/notice/api";
import {formatTimeToRelative} from "#/util/tool";
import {notification} from "ant-design-vue";
import { $t } from "@vben/locales";
const router = useRouter();
const switchAccountModalRef = ref<InstanceType<typeof SwitchAccountModal>>();

View File

@@ -1,83 +1 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import type { SalespersonDashboardTrendItem } from '../api';
const props = defineProps<{
/** 本周期 trend用于环比柱状图「本周期」系列 */
current: SalespersonDashboardTrendItem[];
/** 上一等长周期 compare_trend用于「上周期」系列 */
previous: SalespersonDashboardTrendItem[];
}>();
const chartRef = ref<EchartsUIType>();
const { renderEcharts, resize } = useEcharts(chartRef);
const maxLen = computed(() => Math.max(props.current.length, props.previous.length));
function resolveInputTotal(item: SalespersonDashboardTrendItem) {
return item.input_total ?? item.clinic_count + item.pharmacy_count;
}
/** 按序号对齐本周期与上周期录入总数tooltip 展示真实时间标签 */
async function renderChart() {
const labels = Array.from({ length: maxLen.value }, (_, i) => `T${i + 1}`);
const currentData = props.current.map((item) => resolveInputTotal(item));
const previousData = props.previous.map((item) => resolveInputTotal(item));
await renderEcharts({
tooltip: {
trigger: 'axis',
formatter(params: any) {
const list = Array.isArray(params) ? params : [params];
const idx = list[0]?.dataIndex ?? 0;
const cur = props.current[idx];
const prev = props.previous[idx];
const lines = [`序号 ${idx + 1}`];
<script setup lang="ts">

View File

@@ -1,86 +1 @@
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue';
import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
import type { SalespersonDashboardTrendItem } from '../api';
const props = defineProps<{
/** 来自 dashboardData.trend按时间桶聚合的录入趋势 */
data: SalespersonDashboardTrendItem[];
}>();
const chartRef = ref<EchartsUIType>();
const { renderEcharts, resize } = useEcharts(chartRef);
/** 录入总数兜底:兼容旧接口未返回 input_total 的情况 */
function resolveInputTotal(item: SalespersonDashboardTrendItem) {
return item.input_total ?? item.clinic_count + item.pharmacy_count;
}
/**
* 将 trend 数据映射为 ECharts series
* - 堆叠柱:录入诊所 / 录入药店
* - 折线:录入总数
*/
async function renderChart() {
const labels = props.data.map((item) => item.label);
const clinicData = props.data.map((item) => item.clinic_count);
const pharmacyData = props.data.map((item) => item.pharmacy_count);
const totalData = props.data.map((item) => resolveInputTotal(item));
await renderEcharts({
tooltip: { trigger: 'axis' },
legend: { data: ['录入诊所', '录入药店', '录入总数'] },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: labels,
axisLabel: { rotate: labels.length > 10 ? 45 : 0 },
<script setup lang="ts">

View File

@@ -4,6 +4,12 @@ export default defineConfig(async () => {
return {
application: {},
vite: {
optimizeDeps: {
exclude: [
'@vben/plugins/echarts',
'echarts',
],
},
server: {
proxy: {
'/api': {

8158
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff