feat:
1. 更新框架
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
@@ -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">
|
||||
@@ -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">
|
||||
@@ -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
8158
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user