npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

anytrek-front-public-component

v1.11.30

Published

some common component

Readme

front-public-component

A public component library

📦 Installation

npm install anytrek-front-public-component --save

🚀 Usage

import frontPublicComponent from 'anytrek-front-public-component';
import 'anytrek-front-public-component/style.css';
app.use(frontPublicComponent);

🧩 Component: <anytrekTimerangePick>

Select a time range

<anytrekTimerangePick
  v-model:visible="visible"
  v-model:value="value"
  v-model:valueType="valueType"
  v-model:startTime="startTime"
  v-model:endTime="endTime"
  v-model:timeZone="timeZone"
  :showQuick="true"
  :immediate="true"
  @change="onRangeChange"
/>

🔧 Example Setup

const visible = ref(false);
const value = ref<'custom' | '' | number>('custom');
const valueType = ref<string>('week');
const startTime = ref<string>('');
const endTime = ref<string>('');
const timeZone = ref<string>('');

function onRangeChange(start: string, end: string, tz: string) {
  console.log(start, end, tz);
}

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | value | 当选择快捷选项时,这个字段是数量。value为1,valueType为'month'时表示选择的时间段为从现在开始往前推一个月到现在,当自定义时间时这个字段固定为'custom'。 | custom \| '' \| number | 'custom' | | valueType | 快捷选项的单位(当value值为custom时,这个字段无效) | hour \| day \| week \| month \| quarter \| year | '' | | startTime | 开始时间 | String | '' | | endTime | 结束时间 | String | '' | | valueFormat | startTime、endTime值的格式(如果自定义这个格式没有带上小时,则认为是只选择日期,此时showFormat也应该不带小时及以后部分) | String | YYYY-MM-DDTHH:mm:ssZ | | showFormat | startTime、endTime显示格式 | String | YYYY-MM-DD HH:mm | | timeZone | 时区 | String | 当前网页时区(当needNowTimeZone为false时,timeZone默认取PST) | | visible | popover弹框的展示与否 | Boolean | false | | teleported | 将popover弹框插入至 body 元素 | Boolean | true | | disabled | 禁用选择器 | Boolean | false | | showQuick | 是否显示快捷选项 | Boolean | true | | hourList | 小时快捷选项 | Number[] | [] | | dayList | 天快捷选项 | Number[] | [] | | weekList | 周快捷选项 | Number[] | [1, 2] | | monthList | 月快捷选项 | Number[] | [1] | | quarterList | 季度快捷选项 | Number[] | [] | | yearList | 年快捷选项 | Number[] | [] | | disabledDate | 最多可以选择多少天 | Number | 31 | | immediate | 是否需要开始时执行一次change事件 | Boolean | false | | needNowTimeZone | 是否需要在时区列表显示当前网页时区 | Boolean | true | | haveRangSwitch | 是否需要左右进行区间移动 | Boolean | false | | needMaxIndex | 是否需要弹框在最上层 | Boolean | false | | timeZoneList | 时区选项 | Object[] | [{value:'PST',label:'PST'},{value:'PDT',label:'PDT'},{value:'MST',label:'MST'},{value:'MDT',label:'MDT'},{value:'CST',label:'CST'},{value:'CDT',label:'CDT'},{value:'EST',label:'EST'},{value:'EDT',label:'EDT'},{value:'AST',label:'AST'},{value:'ADT',label:'ADT'},{value:'AKST',label:'AKST'},{value:'AKDT',label:'AKDT'}] | | unlinkPanels | 是否取消两个日期面板之间的联动 | Boolean | true | | isLast | 默认快捷选项都是最近一天,最近一年,要想设置成当天、当月就得把这个值改成false | Boolean | true | | showTimeZoneChoose | 是否允许选择时区 | Boolean | true | | disableDayFuntion | 一个用来判断该日期是否被禁用的函数,接受一个Date对象作为参数。 应该返回一个Boolean值。 | (date:String,tz:String ) => Boolean | | | disableHourFuntion | 禁止选择部分小时选项。数组内有效的值为0-23 | (date:String,tz:String ) => Number[] | | | disableMinuteFuntion | 禁止选择部分分钟选项。数组内有效的值为0-59 | (date:String,hour:String,tz:String ) => Number[] | | | showTime | 没选时间时默认指定哪个月份(最好为15号) | YYYY-MM-DDTHH:mm:ssZ | | | quarterMessage | 当前只能选择哪个季度(季度值取1、2、3、4) | { year: Number, quarter: Number } | | | assetTimeList | 资产利用率数据数组 | { startTime: Number(时间戳或者YYYY-MM-DDTHH:mm:ssZ), endTime: Number(时间戳或者YYYY-MM-DDTHH:mm:ssZ) } | |

🧩 Component: <anytrekTimePick>

Select a time

<anytrekTimePick
  v-model:value="anytrekTimePickValue"
  v-model:timeZone="anytrekTimePickTimeZone"
  v-model:visible="anytrekTimePickVisible"
  :immediate="true"
  @change="timeChange"
/>

🔧 Example Setup

const anytrekTimePickValue = ref<string>('2018-01-31T14:32:19.213Z');
const anytrekTimePickTimeZone = ref<string>('');
const anytrekTimePickVisible = ref<boolean>(false);
function timeChange(time: string, tz: string) {
  console.log(time, tz);
}

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | value | 选择的时间值 | String | '' | | valueFormat | value 值的格式(如果自定义这个格式没有带上小时,则认为是只选择日期,此时showFormat也应该不带小时及以后部分) | String | YYYY-MM-DDTHH:mm:ssZ | | showFormat | value 显示格式 | String | YYYY-MM-DD HH:mm | | timeZone | 时区 | String | 当前网页时区(当needNowTimeZone为false时,timeZone默认取PST) | | visible | popover弹框的展示与否 | Boolean | false | | teleported | 将popover弹框插入至 body 元素 | Boolean | true | | disabled | 禁用选择器 | Boolean | false | | immediate | 是否需要开始时执行一次 change事件 | Boolean | false | | disableFuntion | 一个用来判断该日期是否被禁用的函数,接受一个Date对象作为参数。 应该返回一个Boolean值。 | (date:String,tz:String ) => Boolean | | | needNowTimeZone | 是否需要在时区列表显示当前网页时区 | Boolean | true | | needMaxIndex | 是否需要弹框在最上层 | Boolean | false | | showTimeZoneChoose | 是否允许选择时区 | Boolean | true | | showTime | 没选时间时默认指定哪个月份(最好为15号) | YYYY-MM-DDTHH:mm:ssZ | | | quarterMessage | 当前只能选择哪个季度(季度值取1、2、3、4) | { year: Number, quarter: Number } | |

🧩 Component: <vehicle-select>

Viewing and selecting vehicles

<div style="height: 500px">
  <vehicleSelect
    :data="testData"
    :loading="loading"
    :keyword="keyword"
    :modleList="modleList"
    v-model:sortKey="sortKey"
    v-model:vehicleId="vehicleId"
    v-model:deviceId="deviceId"
    v-model:retract="retract"
  />
</div>

🔧 Example Setup

const testData = ref<any>({});
const loading = ref<boolean>(true);
const keyword = ref<string>('');
const modleList = ref<number[]>([62]);
const sortKey = ref<string>('asc')
const vehicleId = ref<number>(15884)
const deviceId = ref<number>(0)
const retract = ref<boolean>(false);
onMounted(() => {
  testData.value = {
    tagList: [],
    deviceStatusList: [],
    vehicleDeviceList: [],
    landmarkDeviceList: [],
    landmarkList: [],
    vehicleType: [],
    deviceList: [],
    vehicleList: [],
    landmarkGroupList: [],
  }
  loading.value = false;
});

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | keyword | 搜索的关键字,用此字段搜索车辆/设备(右上角搜索框) | String | '' | | loading | 加载中,做状态显示,只要为true,就会出现一层盖在上面的遮罩(第一次loading变成false时,会去合并data中的数据,所以必须在请求到data中的数据之后置为false) | Boolean | true | | data | 接口返回的原始数据(见下方备注) | Object | {} | | sortKey | 排序规则(默认A->Z排序。可选值(driving,idle,waring,offline,battery,last,create,asc,desc)) | String | asc | | modleList | 如果需要只筛选某些型号的设备及其关联的车辆,可以将型号Id加入这个数组 | Number[] | [](比如只想显示型号为2002和2310的设备及其关联的车辆,可以传[37, 54];如果只想显示型号为2308的设备,可以传[62]) | | limitList | 对于modleList的加强限制(见下方备注) | Object[] | [] | | vehicleId | 当前选择的车辆ID(如果匹配不上,会默认到第一辆车)(外部改变选择的车辆/设备靠这个参数和下面的deviceId) | Number | 0 | | deviceId | 当前选择的设备ID(如果上述vehicleId为0且deviceId有值时才生效)(外部改变选择的车辆/设备靠这个参数和上面的vehicleId) | Number | 0 | | immediate | 当vehicleId和deviceId都为0时是否需要选择第一辆车 | Boolean | false | | align | | String | middle | | retract | 外面控制组件关闭的功能 false 打开 true关闭 | Boolean | false | | clikcOutRetract | 是否开启点击组件外部关闭组件的功能 false 关闭 true 打开 | Boolean | false | | showLoading | 整个组件上方显示加载样式(只做样式控制) | Boolean | false | | haveBorderRadius | 四个角是否有圆角 | Boolean | false | | haveLeftBorder | 是否有左边框 | Boolean | false | | isArray | 选择是否以数组的方式进行 | Boolean | false | | vehicleArray | isArray为true时绑定的值,每一项为{ vehicleId:xxx, deviceId: 0, isChoose: Boolean }或者{ vehicleId:0, deviceId: xxx, isChoose: Boolean } | Object[] | [] | | vehicleMaxlength | isArray为true时最多同时选择的车辆数量 | Number | 3 | | showAllName | 收起来之后显示全称 | Boolean | false | | clickToMdvr | 点击跳到mdvr界面 | Function | | | useCompleteVehicleScope | 是否显示全部车辆(此时不显示设备) | Boolean | false | | inMaint | 是否在maint网页(maint网页需要特殊处理,所有状态设备都可以被选) | Boolean | false | | sensorTypeList | 是否只显示绑定了特定传感器的车辆,目前支持的传感器为AS2521、AS2528、AS2530、AS2540 | String[] | [] |

备注:现在data支持2种情况了: 1.包含数组:tagList,deviceStatusList,vehicleDeviceList,landmarkDeviceList,landmarkList,vehicleType,deviceList,vehicleList 2.包含数组:relationDeviceList,tagList,landmarkList,vehicleType

备注:limitList的使用

  1. 必须有modleList才能生效;
  2. 用于对于modelList中的设备详细限制,比如想限制2310固件版本(fwVer)在11309及以上,则可以传 const limitList = ref([ { modelId: 54, key: 'fwVer', regex: '^(1130[9]|113[1-9]\d|11[4-9]\d{2}|1[2-9]\d{3}|[2-9]\d{4}|[1-9]\d{5,})$' } ])

📋 Events Reference

| 事件名 | 描述 | 备注 | | -------- | --------------------- | ----------------- | | clickItem | 通过监听此事件得知车辆/设备选择变化 | 参数为两个(第一个为{vehicleId: xxx,deviceId: xxx},第二个为选择的车辆/设备信息) | | chooseItem | 只有点击车辆后会触发 | 参数为两个(第一个为{vehicleId: xxx,deviceId: xxx},第二个为选择的车辆/设备信息) | | onlyClickChange | 只有点击车辆后会触发 | 参数为两个(第一个为{vehicleId: xxx,deviceId: xxx},第二个为选择的车辆/设备信息) | | clickLandmarkOrTag | 点击tag或者landmark(关闭/打开)触发 | 参数为三个(1.tag/landmark,2.是否关闭,3.选择的tag/landmark信息) | | eventTracking | 触发埋点 | 参数为一个(是具体触发的埋点名称) | | changeArray | isArray为true时车辆选择变化时触发 | 参数为两个(第一个为{vehicleId: xxx,deviceId: xxx}数组,第二个为选择的车辆/设备信息数组) |

🧩 Component: <addOrEditVehicle>

add or edit vehicle

<add-or-edit-vehicle
  v-model="drawerShow"
  :loginInfo="loginInfo"
  :customerHabit="customerHabit"
  :vehicleData="drawerForm"
  :allAssetTypes="allAssetTypes"
  :allVehicles="allVehicles"
  :allDevices="allDevices"
  :modelTypes="modelTypes"
  :allVehicleToDevice="allVehicleToDevice"
  :allTags="allTags"
  :assetTypeFn="assetTypeFn"
  :bindDeviceHis="bindDeviceHis"
  :deviceBindTimeLimit="deviceBindTimeLimit"
  :saveFunctionVehicle="saveFunctionVehicle"
  :allLandmarks="allLandmarks"
  :landmarkGroup="landmarkGroup"
  :saveFunctionTag="saveFunctionTag"
  :unitAllState="unitAllState"
  :GOOGLE_MAP_API_KEY="GOOGLE_MAP_API_KEY"
  :MAP_STYLES="MAP_STYLES"
  :searhTrimblemapAddress="searhTrimblemapAddress"
  :saveFunctionLandmark="saveFunctionLandmark"
  :conDetSensorList="conDetSensorList"
  :needConDetSensor="needConDetSensor"
  ></add-or-edit-vehicle>

🔧 Example Setup

const assetTypeFn = async (data: any, flag: 'add' | 'delete') => {
  if (flag === 'delete') {
    const res = await Api.editCustomizeVehicleType([
      {
        id: data.id,
        isDelete: 1,
      },
    ]);
    if (!res || (res.errorCode && res.errorCode !== 200)) return;
  } else if (flag === 'add') {
    const result = await Api.addCustomizeVehicleType([data]);
    if (!result || result.errorCode) return;
  }
  await getCustomizeVehicleType();
};

const bindDeviceHis = async (params: any) => {
  const res = await Api.loadBoundDevices(params);
  if (!res || (res.errorCode && res.errorCode !== 200)) return;
  return res;
};

const deviceBindTimeLimit = async (deviceId: any) => {
  const res = await Api.getDeviceTimeLimitApi({
    deviceId: deviceId,
  });

  if (!res || res.errorCode) return;

  if (res.length) {
    return [
      res[0].limitStartTime,
      dayjs(new Date()).utc().format('YYYY-MM-DDTHH:mm:ss[Z]'),
    ];
  } else {
    return [null, dayjs(new Date()).utc().format('YYYY-MM-DDTHH:mm:ss[Z]')];
  }
};

const saveFunctionVehicle = async (
  params: any,
  flag = false,
  successFn: Function,
  failFn: Function,
) => {
  let result;
  if (params.id) {
    result = await Api.editVehicleNEW([params]);
  } else {
    result = await Api.addVehicleNEW([params]);
  }

  // 设备状态为1的需要切换为2
  if (flag) {
    const res = await Api.editDeviceNew([
      {
        id: params.deviceId,
        content: {
          deviceStatus: 2,
          modifyTime: dayjs(new Date()).utc().format(),
          activateTime: dayjs(new Date()).utc().format(),
        },
      },
    ]);

    if (!res || res.errorCode) return failFn();
    if (Array.isArray(result) && result[0].errorCode) {
    let errorCode = result[0].errorCode;

    const errLang1 = errLang[errorCode as string];
    const errMessage = errLang1 || `Error Code: ${errorCode}`;

    return failFn(errMessage);
  }
  }

  if (!result || result.errorCode) return failFn();
  successFn();
  refresh();
};

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | drawerShow | 控制drawer的显示和隐藏 | Boolean | false | | loginInfo | 用户信息,包含用户的type和customerId | Object | {} | | customerHabit | 用户的偏好设置,包含localTimezone、timeZone | Object | {} | | vehicleData | 新增/编辑的车辆,编辑是需要传id | Object | { } | | allAssetTypes | 资产类型列表 | Array | [] | | allVehicles | 车辆列表 | Array | [] | | allDevices | 设备列表 | Array | [] | | modelTypes | 设备类型列表 | Object | {} | | allVehicleToDevice | 车辆设备绑定关系列表 | Array | [] | | allTags | tag列表 | Array | [] | | assetTypeFn |资产类型新增/删除方法 | Function | | | bindDeviceHis |车辆绑定历史记录 | Function | | | deviceBindTimeLimit | 设备绑定时间限制 | Function | | | saveFunctionVehicle | 车辆保存 | Function | | | allLandmarks | landmark列表 | Array | [] | | landmarkGroup | landmarkGroup列表 | Array | [] | | saveFunctionTag | tag保存 | Function | | | unitAllState | 城市列表 | Array | [] | | GOOGLE_MAP_API_KEY | 地图apikey | String | | | MAP_STYLES | 地图样式 | Array | [] | | searhTrimblemapAddress |查询地址api | Function | | | saveFunctionLandmark | landmark保存 | Function | | | conDetSensorList | 传感器列表 | Array | [] | | needConDetSensor | 是否显示集装箱检测传感器功能 | Boolean | true |

🧩 Component: <addOrEditTag>

add or edit tag

<add-or-edit-tag
  v-model="drawerShow"
  :loginInfo="loginInfo"
  :customerHabit="customerHabit"
  :itemData="drawerForm"
  :allVehicles="allVehicles"
  :allAssetTypes="allAssetTypes"
  :allDevices="allDevices"
  :modelTypes="modelTypes"
  :allVehicleToDevice="allVehicleToDevice"
  :allTags="allTags"
  :allLandmarks="allLandmarks"
  :landmarkGroup="landmarkGroup"
  :saveFunctionTag="saveFunctionTag"
  :assetTypeFn="assetTypeFn"
  :bindDeviceHis="bindDeviceHis"
  :deviceBindTimeLimit="deviceBindTimeLimit"
  :saveFunctionVehicle="saveFunctionVehicle"
  :unitAllState="unitAllState"
  :GOOGLE_MAP_API_KEY="GOOGLE_MAP_API_KEY"
  :MAP_STYLES="MAP_STYLES"
  :searhTrimblemapAddress="searhTrimblemapAddress"
  :saveFunctionLandmark="saveFunctionLandmark"
  :conDetSensorList="conDetSensorList"
  :needConDetSensor="needConDetSensor"
  ></add-or-edit-tag>

🔧 Example Setup

const saveFunctionTag = async (
  params: any,
  successFn: Function,
  failFn: Function,
) => {
  let result;
  if (params.id) {
    result = await Api.updateTagsList([params]);
  } else {
    result = await Api.saveTagsList([params]);
  }

  if (!result || result.errorCode) return failFn();
  successFn();

  handleClose();
};

const handleAddBurialPoint = (type: string) => {
  Api.addBurialPoint(type);
};

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | drawerShow | 控制drawer的显示和隐藏 | Boolean | false | | loginInfo | 用户信息,包含用户的type和customerId | Object | {} | | customerHabit | 用户的偏好设置,包含localTimezone、timeZone | Object | {} | | itemData | 新增/编辑的tag,编辑是需要传id | Object | { } | | allVehicles | 车辆列表 | Array | [] | | allDevices | 设备列表 | Array | [] | | modelTypes | 设备类型列表 | Object | {} | | allVehicleToDevice | 车辆设备绑定关系列表 | Array | [] | | allTags | tag列表 | Array | [] | | allLandmarks | landmark列表 | Array | [] | | landmarkGroup | landmarkGroup列表 | Array | [] | | saveFunctionTag | tag保存 | Function | | | assetTypeFn |资产类型新增/删除方法 | Function | | | bindDeviceHis |车辆绑定历史记录 | Function | | | deviceBindTimeLimit | 设备绑定时间限制 | Function | | | saveFunctionVehicle | 车辆保存 | Function | | | unitAllState | 城市列表 | Array | [] | | GOOGLE_MAP_API_KEY | 地图apikey | String | | | MAP_STYLES | 地图样式 | Array | [] | | searhTrimblemapAddress |查询地址api | Function | | | saveFunctionLandmark | landmark保存 | Function | | | conDetSensorList | 传感器列表 | Array | [] | | needConDetSensor | 是否显示集装箱检测传感器功能 | Boolean | true |

🧩 Component: <addOrEditLandmark>

add or edit landmark

<add-or-edit-landmark
  v-model="drawerShow"
  :itemData="drawerForm"
  :unitAllState="unitAllState"
  :GOOGLE_MAP_API_KEY="GOOGLE_MAP_API_KEY"
  :MAP_STYLES="MAP_STYLES"
  :allLandmarkGroup="landmarkGroup"
  :searhTrimblemapAddress="searhTrimblemapAddress"
  :saveFunctionLandmark="saveFunctionLandmark"
  @addGroup="handleAddGroup"
  ></add-or-edit-landmark>

🔧 Example Setup

const searhTrimblemapAddress = async (params: any) => {
  const res = await Api.searhTrimblemapAddress(params);
  return res;
};

const saveFunctionLandmark = async (
  params: any,
  successFn: Function,
  failFn: Function,
) => {
  let result;

  if (params.id) {
    result = await Api.editLandmark([params]);
  } else {
    result = await Api.addLandmark([params]);
  }

  if (!result || result.errorCode) return failFn();
  successFn();
  initData();
};

const handleAddGroup = (params: any) => {
  groupFormVisible.value = true;
  groupForm = params;
};

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | drawerShow | 控制drawer的显示和隐藏 | Boolean | false | | itemData | 新增/编辑的landmark | Object | | | allLandmarks | landmark列表 | Array | [] | | unitAllState | 城市列表 | Array | [] | | GOOGLE_MAP_API_KEY | 地图apikey | String | | | MAP_STYLES | 地图样式 | Array | [] | | allLandmarkGroup | landmarkGroup列表 | Array | [] | | searhTrimblemapAddress |查询地址api | Function | | | saveFunctionLandmark | landmark保存 | Function | | | @addGroup | 新增group | Function | |

🧩 Component: <AddOrEditShare>

add or edit Share

<AddOrEditShare
  v-model="addVisible"
  :vehicleLoading="vehicleLoading"
  :readOnly=""
  :data="testData"
  :itemData="itemData"
  :unitAllState="unitAllState"
  :getShareHistoryUrl="getShareHistoryUrl"
  :uploadShareFile="uploadShareFile"
  :saveFunctionShare="saveFunctionShare"
  :loginInfo="loginInfo"
  :assetTypeFn="assetTypeFn"
  :bindDeviceHis="bindDeviceHis"
  :deviceBindTimeLimit="deviceBindTimeLimit"
  :saveFunctionVehicle="saveFunctionVehicle"
  :saveFunctionTag="saveFunctionTag"
  :unitAllState="unitAllState"
  :GOOGLE_MAP_API_KEY="GOOGLE_MAP_API_KEY"
  :MAP_STYLES="MAP_STYLES"
  :searhTrimblemapAddress="searhTrimblemapAddress"
  :saveFunctionLandmark="saveFunctionLandmark"
  ></AddOrEditShare>

🔧 Example Setup

const addVisible = ref(false);
const vehicleLoading = ref(false);
const testData = ref<any>({});
onMounted(() => {
  testData.value = {
    tagList: [],
    deviceStatusList: [],
    vehicleDeviceList: [],
    landmarkDeviceList: [],
    landmarkList: [],
    vehicleType: [],
    deviceList: [],
    vehicleList: [],
    landmarkGroupList: [],
  }
  vehicleLoading.value = false;
});
const readOnly = ref(false);
const itemData = ref<any>({});
const getShareHistoryUrl = Api.getHistoryUrl;
const uploadShareFile = Api.uploadFile;

const saveFunctionShare = async (
  params: any,
  successFn: Function,
  failFn: Function,
) => {
  let result;

  if (params.id) {
    result = await Api.editShare([params]);
  } else {
    result = await Api.addShare([params]);
  }

  if (!result || result.errorCode) return failFn();
  successFn();
  initData();
};
const { loginInfo } = store;

const assetTypeFn = async (data: any, flag: 'add' | 'delete') => {
  if (flag === 'delete') {
    const res = await Api.editCustomizeVehicleType([
      {
        id: data.id,
        isDelete: 1,
      },
    ]);
    if (!res || (res.errorCode && res.errorCode !== 200)) return;
  } else if (flag === 'add') {
    const result = await Api.addCustomizeVehicleType([data]);
    if (!result || result.errorCode) return;
  }
  await getCustomizeVehicleType();
};

const bindDeviceHis = async (params: any) => {
  const res = await Api.loadBoundDevices(params);
  if (!res || (res.errorCode && res.errorCode !== 200)) return;
  return res;
};

const deviceBindTimeLimit = async (deviceId: any) => {
  const res = await Api.getDeviceTimeLimitApi({
    deviceId: deviceId,
  });

  if (!res || res.errorCode) return;

  if (res.length) {
    return [
      res[0].limitStartTime,
      dayjs(new Date()).utc().format('YYYY-MM-DDTHH:mm:ss[Z]'),
    ];
  } else {
    return [null, dayjs(new Date()).utc().format('YYYY-MM-DDTHH:mm:ss[Z]')];
  }
};

const saveFunctionVehicle = async (
  params: any,
  flag = false,
  successFn: Function,
  failFn: Function,
) => {
  let result;
  if (params.id) {
    result = await Api.editVehicleNEW([params]);
  } else {
    result = await Api.addVehicleNEW([params]);
  }

  // 设备状态为1的需要切换为2
  if (flag) {
    const res = await Api.editDeviceNew([
      {
        id: params.deviceId,
        content: {
          deviceStatus: 2,
          modifyTime: dayjs(new Date()).utc().format(),
          activateTime: dayjs(new Date()).utc().format(),
        },
      },
    ]);

    if (!res || res.errorCode) return failFn();
  }

  if (!result || result.errorCode) return failFn();
  successFn();
  refresh();
};

const saveFunctionTag = async (
  params: any,
  successFn: Function,
  failFn: Function,
) => {
  let result;
  if (params.id) {
    result = await Api.updateTagsList([params]);
  } else {
    result = await Api.saveTagsList([params]);
  }

  if (!result || result.errorCode) return failFn();
  successFn();

  handleClose();
};

const unitAllState = ref<any[]>([]);
const GOOGLE_MAP_API_KEY: string = 'xxxx;
const MAP_STYLES: any = {};

const searhTrimblemapAddress = async (params: any) => {
  const res = await Api.searhTrimblemapAddress(params);
  return res;
};

const saveFunctionLandmark = async (
  params: any,
  successFn: Function,
  failFn: Function,
) => {
  let result;

  if (params.id) {
    result = await Api.editLandmark([params]);
  } else {
    result = await Api.addLandmark([params]);
  }

  if (!result || result.errorCode) return failFn();
  successFn();
  initData();
};

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | | ---------- | --------------------- | ---------------- | ------------------- | | modelValue | 控制drawer的显示和隐藏 | Boolean | false | | readOnly | 是否只看 | Boolean | false | | data | 接口返回的原始数据(包含数组:tagList,deviceStatusList,vehicleDeviceList,landmarkDeviceList,landmarkList,vehicleType,deviceList,vehicleList,landmarkGroupList) | Object | {} | | itemData | 新增/编辑的share | Object | {} | | getShareHistoryUrl | 获取分享的历史logo | Function | | | uploadShareFile | 上传logo | Function | | | saveFunctionShare | share保存 | Function | | | loginInfo | 登录信息 | Object | {} | | assetTypeFn |资产类型新增/删除方法 | Function | | | bindDeviceHis |车辆绑定历史记录 | Function | | | deviceBindTimeLimit | 设备绑定时间限制 | Function | | | saveFunctionVehicle | 车辆保存 | Function | | | saveFunctionTag | tag保存 | Function | | | unitAllState | 城市列表 | Array | [] | | GOOGLE_MAP_API_KEY | 地图apikey | String | | | MAP_STYLES | 地图样式 | Array | [] | | searhTrimblemapAddress |查询地址api | Function | | | saveFunctionLandmark | landmark保存 | Function | | | nowChooseVehicleId | 当前选择的车辆id | Number | |

🧩 Component: <ImageDisplay>

图片预览弹窗,支持缩放、旋转、地图 MiniMap、AI 分析、图片列表懒加载、多帧图片轮播

<ImageDisplay
  ref="imageDisplayRef"
  v-model="visible"
  :data="imageData"
  :vehicleId="vehicleId"
  :deviceId="deviceId"
  :curImgList="imgList"
  :curPicWH="curPicWH"
  :haveRotate="haveRotate"
  :saveRotation="saveRotation"
  :haveMap="haveMap"
  :needPath="needPath"
  :mapJson="mapJson"
  :childImages="childImages"
  :isMutiple="isMutiple"
  :google-map-key="googleMapKey"
  :on-get-history-trail="fetchHistoryTrail"
  :on-get-history-trail-point="fetchHistoryTrailPoint"
  :get-img-list="fetchImgList"
  @handle-close="onClose"
  @handle-download="onDownload"
  @update-cur-img="onUpdateCurImg"
  @updateChildUrl="onUpdateChildUrl"
  @to-history="onToHistory"
/>

🔧 Example Setup

const imageDisplayRef = ref();
const visible = ref(false)
const imageData = ref<any>({})
const previewItem = ref<any>({})//当前设备
const imgList = ref<any[]>([]);
const childImages = ref<any[]>([]);
const isMutiple = ref(false);

/* 图片宽高 */
const curPicWH = ref<any>({
  imgW: 800,
  imgH: 600,
});
const showImageDisplay = () => {
  imageDisplayRef.value?.loadImageListFun();

  visible.value = true;
}
/* CargoHandling 相同 uuid 归为一项,新增 imageList 字段存所有帧按 takeTime 最新排序 */
const groupCargoHandlingImages = (list: any[]): any[] => {
  const groupMap = new Map<string, any[]>();
  list.forEach((item: any) => {
    if (item.triggerType === 'CargoHandling' && item.uuid) {
      const key = item.uuid;
      if (!groupMap.has(key)) groupMap.set(key, []);
      groupMap.get(key)!.push(item);
    }
  });
  const groupedUuids = new Set(groupMap.keys());
  const result: any[] = [];
  groupMap.forEach((items) => {
    items.sort((a: any, b: any) => new Date(b.takeTime).getTime() - new Date(a.takeTime).getTime());
    result.push({ ...items[0], imageList: [...items] });
  });
  list.forEach((item: any) => {
    if (!(item.triggerType === 'CargoHandling' && item.uuid && groupedUuids.has(item.uuid))) {
      result.push(item);
    }
  });
  result.sort((a, b) => new Date(b.takeTime).getTime() - new Date(a.takeTime).getTime());
  return result;
}

const fetchImgList = async (successFun: Function) => { 
  /* 请求图片列表 */ 
  const res = await getImgList();
  if (!res || res.errorCode) {
    return;
  }
  const list = groupCargoHandlingImages(res);
  if (list[0] && imageData.value?.deviceId !== previewItem.value?.deviceId) {
    imageData.value = { ...list[0] };
    childImages.value = imageData.value.imageList?.map((i: any) => i.url) || [];
    isMutiple.value = imageData.value.imageList?.length > 1;
  }
  imgList.value = list;
  successFun();
}

const onDownload = (data: any) => {
  /* 下载图片,自行根据data数组长度执行下载方法 */ 

}

onUpdateCurImg: (index: number) => {
  const img = { ...imgList.value[index] };
  delete img._mainUrl;
  imageData.value = img;
  isMutiple.value = !!(img?.imageList?.length);
  childImages.value = img?.imageList?.map((i: any) => i.url) || [];
},
onUpdateChildUrl: (url: string) => {
  const img = imageData.value;
  if (!img._mainUrl) {
    img._mainUrl = img.url;
  }
  if (img.imageList) {
    const child = img.imageList.find((i: any) => i.url === url);
    if (child) {
        Object.assign(img, child);
        img.url = url;
    }
  }
},

const fetchHistoryTrail = async () => { 
  const res = await getHistoryTrail();
  if (!res || res.errorCode) {
    return [];
  }
  return res;
}

const fetchHistoryTrailPoint = async () => { 
  const res = await getHistoryTrailPoint();
  if (!res || res.errorCode) {
    return [];
  }
  return res;
}

const onToHistory = (data: any) => {
  const { startTime, endTime, takeTime, vehicleId, deviceId, } = data;
  let url =
    getOrigin() +
    `/#/history-trail?startTime=${startTime}&endTime=${endTime}&takeTime=${takeTime}`;

  if (vehicleId) {
    url += `&vehicleId=${vehicleId}`;
  }
  if (deviceId) {
    url += `&deviceId=${deviceId}`;
  }
  console.log(url);
  location.href = url;
}

📋 Props Reference

| 参数 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | | modelValue | 弹窗显示/隐藏 | Boolean | false | | data | 当前图片数据 | Object | '' | | vehicleId | 车辆 ID | String \| Number | '' | | deviceId | 设备 ID | String \| Number | '' | | curImgList | 图片列表 | Object[] | [] | | curPicWH | 图片宽高 | Object | {imgW: 800,imgH: 600} | | haveRotate | 是否可以旋转角度 | Boolean | false | | saveRotation | 是否持久化旋转角度 | Boolean | false | | haveMp | 是否显示地图 | Boolean | false | | needPath | 是否显示轨迹 | Boolean | false | | mapJson | Google Maps style | Object | | | `googleMapKey` | Google Maps API Key | `String` | | | onGetHistoryTrail | 获取历史轨迹 | Function | — | | onGetHistoryTrailPoint | 获取轨迹点详情 | Function | — | | getImgList | 获取图片列表(弹窗打开且列表为空时调用) | Function | — | | isMutiple | 是否开启多帧图片轮播模式 | Boolean | false | | childImages | 子图 URL 列表(多帧轮播时使用) | String[] | [] |

📋 Events Reference

| 事件 | 描述 | 参数 | | --- | --- | --- | | update:modelValue | 弹窗开关变化 | Boolean | | handleClose | 弹窗关闭 | — | | handleDownload | 下载图片,始终返回数组,父组件通过数组长度判断下载方式 | data: Object[] | | updateCurImg | 切换图片 | index: Number | | toHistory | 跳转轨迹历史 | params: Object | | updateChildUrl | 多帧轮播切换子图时触发 | url: String |

📋 Methods Reference (通过 ref 调用)

| 方法名 | 描述 | 参数 | | --- | --- | --- | | loadImageListFun | 请求图片列表,它会调传进来的getImgList方法 | — |

🧩 Component: <SelectList>

列表选择

<SelectList
  v-model:visible="isShowAllList"
  v-model:selected="selectedIds"
  :items="allTagsList"
  id-key="id"
  name-key="name"
  :search-placeholder="placeholderText"
  :batch-mode="hasMoreInput"
  :single-mode-label="
    eleType === 'vehicle' ? t('vehicleName') : t('device')
  "
  :batch-mode-label="t('batchDeviceID')"
  batch-match-field="deviceUpid"
  :create-label="wordAboutAdd"
  @confirm="handleConfirm"
  @jump="handleJumpEdit"
  @create="handleClickAdd"
>
  <template #item-icon="{ item }">
    <anSvgIcon
      :name="
        addtagType === 'vehicle'
          ? getVehicleIconById(item.id)
          : setTagSvg(addtagType, item)
      "
      font-size="24"
    />
  </template>
</SelectList>

🔧 Example Setup

import { ref, computed } from 'vue';

// 面板控制
const isShowAllList = ref(false);
const selectedIds = ref<any[]>([]);

// 数据源(示例:标签列表)
const allTagsList = ref([
  { id: 1, name: '标签A' },
  { id: 2, name: '标签B' },
]);

// 搜索 & 输入模式
const placeholderText = ref('搜索标签');
const hasMoreInput = ref(false);
const eleType = ref('tag');
const addtagType = ref('tag');
const wordAboutAdd = ref('新建标签');

// 确认选中
function handleConfirm(items: any[]) {
  // 将选中的 items 合并到表单数据
}

// 跳转编辑
function handleJumpEdit(item: any) {
  console.log('edit', item);
}

// 新建
function handleClickAdd() {
  console.log('create');
}
// 图标辅助函数(按项目类型返回对应 SVG)
function setTagSvg(type: string) {
  return type === 'landmark'
    ? 'landmarkIcon'
    : type === 'vehicle'
      ? 'vehicleIcon'
      : 'tagIcon';
}

function getVehicleIconById(id: number) {
  const vehicle = allVehicles.value.find((v: any) => v.id === id);
  return vehicle ? 'vehicle-' + vehicle.content.newIcon : 'vehicleIcon';
}

📋 Props Reference

| 参数名称 | 描述 | 数据类型 | 默认值 | 必填 | | --- | --- | --- | --- | --- | | visible | 控制面板显隐 | Boolean | — | ✅ | | selected | 已选中项的 ID 数组(双向绑定) | Array<string \| number> | [] | — | | items | 数据源列表;tag时,需要过滤掉当前tag | Array | [] | — | | idKey | 唯一标识字段名 | String | "id" | — | | nameKey | 显示名称字段名 | String | "name" | — | | searchable | 是否显示搜索框 | Boolean | true | — | | searchPlaceholder | 搜索框占位文本,不传时默认使用多语言 | String | "" | — | | batchMode | 是否启用"单个/批量"输入模式切换 | Boolean | false | — | | singleModeLabel | 单个输入模式的标签文字 | String | "" | — | | batchModeLabel | 批量输入模式的标签文字 | String | "" | — | | batchMatchField | 批量模式下精确匹配的字段名,默认取 idKey;对于车辆和设备,需要传入deviceUpid字段,值为userPackageId | String | "" | — | | showSelectAll | 是否显示全选行 | Boolean | true | — | | showJump | 是否显示每项的跳转编辑按钮 | Boolean | true | — | | showCreate | 是否显示底部新建按钮 | Boolean | true | — | | createLabel | 新建按钮文字 | String | "" | — | | virtual | 是否启用虚拟滚动(大数据量推荐开启) | Boolean | true | — | | itemHeight | 虚拟滚动单项高度(px) | Number | 44 | — | | maxHeight | 列表最大高度(px) | Number | 320 | — | | top | 面板 CSS top 定位值 | String | "50%" | — | | right | 面板 CSS right 定位值 | String | "525px" | — | | zIndex | 面板 CSS z-index 层级 | Number | 1000 | — |

📤 Events

| 事件名 | 参数 | 描述 | | --- | --- | --- | | update:visible | Boolean | 面板显隐状态变化 | | update:selected | Array | 选中项 ID 数组变化 | | confirm | Array | 点击确认按钮,返回当前选中的完整 item 数组 | | jump | item | 点击某项的跳转编辑按钮,返回当前 item | | create | — | 点击底部新建按钮 |

🧩 Slots

| 插槽名 | 作用域 | 描述 | | --- | --- | --- | | header-left | — | 头部左侧区域,默认显示已选数量 | | header-actions | — | 头部右侧操作按钮区域 | | item-icon | { item } | 每项左侧图标 | | item-label | { item } | 每项显示文本,默认取 item[nameKey] | | item-right | { item } | 每项右侧额外内容 | | empty | — | 列表为空时的占位内容 | | footer | — | 底部自定义区域,会覆盖默认的新建按钮 |