添加新的需求

This commit is contained in:
xuqiuyun
2025-10-20 17:32:09 +08:00
parent 9979e00b47
commit 361d5ab1ae
247 changed files with 34249 additions and 1 deletions

View File

@@ -0,0 +1,122 @@
<template>
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
@onChange="handleChange"
@onDestroyed="handleDestroyed"
@onFocus="handleFocus"
@onBlur="handleBlur"
@customAlert="customAlert"
@customPaste="customPaste"
/>
</div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted, watch } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import { upload } from '@/api/common/index';
const props = defineProps({
html: {
required: true,
type: String,
default: '',
},
});
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref('');
const mode = ref('default');
watch(
() => props.html,
(val) => {
valueHtml.value = val;
},
{
immediate: true,
deep: true,
},
);
// 模拟 ajax 异步获取内容
onMounted(() => {});
const toolbarConfig = {
excludeKeys: ['insertLink', 'viewImageLink', 'insertVideo', 'uploadVideo', 'emotion', 'fullScreen', 'codeBlock', 'todo'],
};
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
async customUpload(file, insertFn) {
const formData = new FormData(); // 组建formData参数
formData.append('file', file); // 将图片文件添加formData参数中
upload(formData)
.then((ret) => {
insertFn(ret.data.src, ret.data.src, '');
})
.catch(() => {
target.value = '';
});
},
},
},
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor) => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
const emits = defineEmits();
const handleChange = (editor) => {
emits('update:html', valueHtml.value);
};
const handleFocus = (editor) => {
// console.log('focus', editor);
};
const handleBlur = (editor) => {
// console.log('blur', editor);
// console.log(valueHtml.value);
};
const customAlert = (info, type) => {
// alert(`【自定义提示】${type} - ${info}`);
};
const customPaste = (editor, event, callback) => {
const html = event.clipboardData.getData('text/html'); // 获取粘贴的 html
editor.dangerouslyInsertHtml(html); // 显示编译后的html
event.preventDefault();
callback(false);
};
const handleDestroyed = (editor) => {
// console.log('destroyed', editor);
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>
<style lang="less" scoped>
.w-e-modal button {
line-height: 30px !important;
}
.group-video {
display: none !important;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="icon-body">
<el-input v-model="iconName" class="icon-search" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons">
<template #suffix><i class="el-icon-search el-input__icon" /></template>
</el-input>
<div class="icon-list">
<div class="list-container">
<div v-for="(item, index) in iconList" :key="index" class="icon-item-wrapper" @click="selectedIcon(item)">
<div :class="['icon-item', { active: activeIcon === item }]">
<svg-icon :icon-class="item" class-name="icon" style="height: 25px; width: 16px" />
<span>{{ item }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import icons from './requireIcons';
import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({
activeIcon: {
type: String,
},
});
const iconName = ref('');
const iconList = ref(icons);
const emit = defineEmits(['selected']);
function filterIcons() {
iconList.value = icons;
if (iconName.value) {
iconList.value = icons.filter((item) => item.indexOf(iconName.value) !== -1);
}
}
function selectedIcon(name) {
emit('selected', name);
document.body.click();
}
function reset() {
iconName.value = '';
iconList.value = icons;
}
defineExpose({
reset,
});
</script>
<style lang="scss" scoped>
.icon-body {
width: 100%;
padding: 10px;
.icon-search {
position: relative;
margin-bottom: 5px;
}
.icon-list {
height: 200px;
overflow: auto;
.list-container {
display: flex;
flex-wrap: wrap;
.icon-item-wrapper {
width: calc(100% / 3);
height: 25px;
line-height: 25px;
cursor: pointer;
display: flex;
.icon-item {
display: flex;
max-width: 100%;
height: 100%;
padding: 0 5px;
&:hover {
background: #ececec;
border-radius: 5px;
}
.icon {
flex-shrink: 0;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
padding-left: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.icon-item.active {
background: #ececec;
border-radius: 5px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,8 @@
const icons = [];
const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
icons.push(p);
}
export default icons;

View File

@@ -0,0 +1,332 @@
<template>
<section class="upload-component">
<VueDraggable ref="VueDraggableRef" v-model="imgUrl" class="flex" v-if="canSort" @end="onDragEnd">
<template v-for="(item, idx) in imgUrl" :key="item">
<div style="margin-bottom: 10px">
<div class="uploadWrapBorder">
<div class="has_uploaded_img">
<video :src="item" v-if="item.includes('mp4')"></video>
<img :src="item" v-else />
<div class="mask"></div>
<div class="operation">
<el-icon @click="onPreviewImg(idx)">
<ZoomIn />
</el-icon>
<el-icon @click="onDeleteImg(idx)" v-if="!canUpload">
<Delete />
</el-icon>
</div>
</div>
</div>
</div>
</template>
</VueDraggable>
<template v-for="(item, idx) in imgUrl" v-else>
<div class="uploadWrapBorder">
<div class="has_uploaded_img">
<!-- 判断一下item是不是video -->
<video autoplay="true" :src="item" v-if="item.includes('mp4')"></video>
<img :src="item" v-else />
<div class="mask"></div>
<div class="operation">
<el-icon @click="onPreviewImg(idx)">
<ZoomIn />
</el-icon>
<el-icon @click="onDeleteImg(idx)" v-if="!canUpload">
<Delete />
</el-icon>
</div>
</div>
</div>
</template>
<div
v-if="imgUrl.length < imgCount && !canUpload"
v-loading="imgUploading"
class="uploadWrapBorder"
element-loading-background="rgba(0, 0, 0, 0.5)"
>
<div class="no_upload" @click="onUploadClick">
<el-icon>
<Plus />
</el-icon>
</div>
</div>
<input ref="InputFileRef" :accept="acceptType" :maxFileSize="maxFileSize" type="file" @change="onUploadInputChange($event)" />
<template v-if="acceptType == 'image/*'">
<el-image-viewer v-if="showImageViewer" :url-list="imgViewUrl" @close="imgPreviewClose" :initial-index="showIndex" />
</template>
<template v-else>
<el-dialog v-model="dialogVisible" title="" width="50%">
<div>
<video :src="imgViewUrl[0]" autoplay controls="controls" style="height: 800px; width: 100%"></video>
</div>
<!-- <el-carousel indicator-position="none" motion-blur height="800px" width="100%">
<el-carousel-item v-for="(item, index) in imgViewUrl.length" :key="item" width="100%">
<video controls="controls" autoplay :style="{ height: '780px', width: '100%', objectFit: 'contain' }">
<source :src="imgViewUrl[index]" type="video/mp4" />
</video>
</el-carousel-item>
</el-carousel> -->
</el-dialog>
</template>
</section>
</template>
<script>
import { computed, defineComponent, nextTick, reactive, ref, toRefs, watch } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import { upload } from '~/api/common/index.js';
export default defineComponent({
components: { VueDraggable },
props: {
avatar: {
type: Array,
default: [],
required: true,
},
limit: {
type: Number,
default: 1,
reqiured: false,
},
isDetail: {
type: Boolean,
default: false,
required: false,
},
sortAble: {
type: Boolean,
default: false,
required: false,
},
accept: {
type: String,
default: 'image/*',
required: false,
},
maxFileSize: {
// 新增最大文件大小属性
type: Number,
default: 2 * 1024 * 1024, // 默认值为2MB
required: false,
},
},
setup(props, ctx) {
const dialogVisible = ref(false);
const InputFileRef = ref(null);
const imgUrl = ref(props.avatar);
const imgUploading = ref(false);
const showImageViewer = ref(false);
const imgViewUrl = ref([]);
const showIndex = ref(0);
const canSort = computed(() => props.sortAble);
const acceptType = computed(() => {
return props.accept;
});
const imgCount = computed(() => {
return props.limit;
});
const canUpload = computed(() => {
return props.isDetail;
});
const maxFileSizeType = computed(() => props.maxFileSize); // 新增计算属性
const VueDraggableRef = ref(null);
// const onDragEnd = () => {
// VueDraggableRef.value.onDragEnd();
// };
const ComponentInteraction = reactive({
onDragEnd: () => {
ctx.emit('update:avatar', imgUrl.value);
},
onUploadClick: () => {
if (InputFileRef.value) {
InputFileRef.value.click();
}
},
onUploadInputChange: (e) => {
const { target } = e;
const val = target.value;
if (val !== '') {
const file = target.files[0];
if (file && file.size <= maxFileSizeType.value) {
// 检查文件大小
imgUploading.value = true;
const formData = new FormData();
formData.append('file', file);
upload(formData)
.then((ret) => {
imgUrl.value.push(ret.data.src);
target.value = '';
ctx.emit('update:avatar', imgUrl.value);
imgUploading.value = false;
})
.catch((err) => {
// console.log(err);
imgUploading.value = false;
});
} else {
alert(`文件过大,请选择不超过${(maxFileSizeType.value / 1024 / 1024).toFixed(2)}MB的文件`);
imgUploading.value = false;
}
}
},
onDeleteImg: (idx) => {
imgUrl.value.splice(idx, 1);
ctx.emit('update:avatar', imgUrl.value);
},
onPreviewImg: (idx) => {
showIndex.value = idx;
imgViewUrl.value = imgUrl.value;
showImageViewer.value = true;
if (acceptType.value == 'video/*') {
dialogVisible.value = true;
}
// console.log(acceptType.value);
// console.log(imgUrl.value);
},
imgPreviewClose: () => {
showImageViewer.value = false;
},
});
// onMounted(() => {
// imgUrl.value = props.avatar;
// });
watch(
() => props.avatar,
(nVal) => {
imgUrl.value = nVal;
},
{
immediate: true,
},
);
return {
InputFileRef,
imgUrl,
imgUploading,
imgCount,
...toRefs(ComponentInteraction),
showImageViewer,
imgViewUrl,
dialogVisible,
showIndex,
canUpload,
canSort,
acceptType,
VueDraggableRef,
};
},
});
</script>
<style lang="scss" scoped>
::v-deep .el-dialog__body {
height: 800px;
}
section.upload-component {
display: flex;
width: 94%;
.flex {
flex-wrap: wrap;
}
div.uploadWrapBorder {
margin-right: 10px;
width: 120px;
height: 120px;
background: #ffffff;
border-radius: 5px;
border: 1px solid #dcdfe6;
text-align: center;
line-height: 80px;
cursor: pointer;
border-radius: 2px;
position: relative;
overflow: hidden;
div.no_upload {
@apply flex items-center justify-center;
position: absolute;
width: 100%;
height: 100%;
i.iconfont {
font-size: var(--el-strong-font-size);
font-weight: bold;
}
}
div.no_upload:hover {
i.iconfont {
color: var(--el-color-primary);
}
}
div.has_uploaded_img {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
> img {
position: absolute;
width: 100%;
left: 0px;
top: 50%;
transform: translateY(-50%);
}
div.mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.6;
z-index: 1;
display: none;
}
div.operation {
@apply w-full h-full items-center justify-center;
z-index: 10;
display: none;
position: absolute;
left: 0px;
top: 0px;
i {
margin-right: 10px;
font-size: 20px;
color: #fff;
font-weight: bold;
}
i:last-child {
margin-right: 0px;
}
}
}
div.has_uploaded_img:hover {
div.mask,
div.operation {
display: flex;
}
}
}
input {
display: none;
}
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div :style="'height:' + height">
<iframe :id="iframeId" style="width: 100%; height: 100%" :src="src" frameborder="no"></iframe>
</div>
</template>
<script setup>
const props = defineProps({
src: {
type: String,
default: '/',
},
iframeId: {
type: String,
},
});
const height = ref(`${document.documentElement.clientHeight - 94.5}px`);
</script>

View File

@@ -0,0 +1,81 @@
<template>
<span v-for="(digit, index) in digits" :key="index" class="digit">{{ digit }}</span>
</template>
<script>
import { ref, watch, onMounted } from 'vue';
export default {
props: {
number: {
type: Number,
required: true,
},
duration: {
type: Number,
default: 1000,
},
},
setup(props) {
const digits = ref(Array.from({ length: props.number.toString().length }, () => 0));
const animateDigits = () => {
const targetNumber = props.number;
const { duration } = props;
const totalFrames = 100;
let increment;
if (Number.isInteger(targetNumber)) {
increment = Math.floor(targetNumber - Number(digits.value.join(''))) / totalFrames;
} else {
increment = (targetNumber - Number(digits.value.join(''))) / totalFrames;
}
let currentNumber = Number(digits.value.join(''));
const interval = setInterval(() => {
currentNumber += increment;
if ((increment > 0 && currentNumber >= targetNumber) || (increment < 0 && currentNumber <= targetNumber)) {
currentNumber = targetNumber;
clearInterval(interval);
}
const roundedNumber = Math.round(currentNumber * 100) / 100; // 保留两位小数
const splitNumber = roundedNumber.toString().split('.');
const integerPart = splitNumber[0];
const decimalPart = splitNumber[1] || '';
const integerDigits = integerPart.split('').map(Number);
const decimalDigits = decimalPart.split('').map(Number);
digits.value = [];
digits.value = digits.value.concat(integerDigits);
if (decimalPart !== '') {
digits.value.push('.');
digits.value = digits.value.concat(decimalDigits);
}
}, duration / totalFrames);
};
onMounted(() => {
animateDigits();
});
watch(
() => props.number,
() => {
animateDigits();
},
);
return { digits };
},
};
</script>
<style>
.digit {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="Pagination-Container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { scrollTo } from '~/utils/scrollTo.js';
// eslint-disable-next-line no-undef
const props = defineProps({
total: {
required: true,
type: Number,
},
page: {
type: Number,
default: 1,
},
limit: {
type: Number,
default: 20,
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50, 100];
},
},
// 移动端页码按钮的数量端默认值5
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7,
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next,jumper',
},
background: {
type: Boolean,
default: true,
},
autoScroll: {
type: Boolean,
default: true,
},
hidden: {
type: Boolean,
default: false,
},
});
// eslint-disable-next-line no-undef
const emit = defineEmits();
const currentPage = computed({
get() {
return props.page;
},
set(val) {
emit('update:page', val);
},
});
const pageSize = computed({
get() {
return props.limit;
},
set(val) {
emit('update:limit', val);
},
});
function handleSizeChange(val) {
if (currentPage.value * val > props.total) {
currentPage.value = 1;
}
emit('pagination', { page: currentPage.value, limit: val });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
function handleCurrentChange(val) {
emit('pagination', { page: val, limit: pageSize.value });
if (props.autoScroll) {
scrollTo(0, 800);
}
}
</script>
<style lang="scss" scoped>
div.Pagination-Container {
@apply py-5 text-right flex;
justify-content: end;
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@@ -0,0 +1,50 @@
<template>
<el-cascader
ref="CascaderRegionRef"
:options="data.options"
:props="cascaderProps"
:show-all-levels="true"
clearable
collapse-tags
placeholder="请选择"
style="width: 200px"
/>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import regionList from '~/api/region.js';
import { handleTree } from '~/utils/aiotagro.js';
const CascaderRegionRef = ref(null);
const data = reactive({
options: [],
});
const cascaderProps = {
multiple: false,
label: 'name',
value: 'name',
};
const handlers = reactive({
getRegionList() {
regionList().then((ret) => {
const list = ret.data.map((item) => {
return {
id: item.id,
pid: item.pid,
name: item.name,
};
});
data.options = handleTree(list);
// console.log('====================================');
// console.log(data.options);
});
},
});
onMounted(() => {
handlers.getRegionList();
});
</script>

View File

@@ -0,0 +1,55 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :fill="color" :xlink:href="iconName" />
</svg>
</template>
<script>
import { computed, defineComponent } from 'vue';
export default defineComponent({
props: {
iconClass: {
// type: String,
required: true,
},
className: {
type: String,
default: '',
},
color: {
type: String,
default: '',
},
},
setup(props) {
return {
iconName: computed(() => `#icon-${props.iconClass}`),
svgClass: computed(() => {
if (props.className) {
return `svg-icon ${props.className}`;
}
return 'svg-icon';
}),
};
},
});
</script>
<style lang="scss" scope>
.sub-el-icon,
.nav-icon {
display: inline-block;
font-size: 15px;
margin-right: 12px;
position: relative;
}
.svg-icon {
width: 1em;
height: 1em;
position: relative;
fill: currentColor;
vertical-align: -2px;
}
</style>

View File

@@ -0,0 +1,10 @@
import * as components from '@element-plus/icons-vue';
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};

View File

@@ -0,0 +1,312 @@
<template>
<div class="dialog-search">
<el-form :inline="true" ref="ruleForm" label-position="right" :model="formInline" class="demo-form-inline top-screen" v-if="formInline">
<div
class="ruleForm_left"
:style="
openBy
? 'height:' + Math.ceil(formItemList.map((item) => item.span || 6).reduce((a, b) => a + b) / 24) * 62 + 'px;'
: 'height:62px;'
"
>
<el-row :gutter="24">
<el-col :span="item.span || 6" v-for="(item, index) in props.formItemList" :key="index + '' + item">
<el-form-item :label-width="(item.labelWidth || 80) + 'px'" :label="isShowLabel ? item.label + ':' : ''">
<!-- 下拉选择框 -->
<el-select
v-if="item.type == 'select'"
v-model="formInline[item.param]"
:multiple="item.multiple"
:placeholder="item.placeholder || '请选择' + item.label"
clearable
@clear="() => clear(formInline[item.param], item.param)"
@change="() => change(formInline[item.param], item.param)"
>
<el-option
v-for="(item2, index2) in item.selectOptions"
:key="index2"
:label="item2[item.labelKey || 'text']"
:value="item2[item.valueKey || 'value']"
></el-option>
</el-select>
<!-- 下拉选择框end -->
<!-- 输入框 -->
<el-input
v-if="item.type == 'input'"
v-model="formInline[item.param]"
:placeholder="item.placeholder || '请输入' + item.label"
@clear="() => clear(formInline[item.param], item.param)"
@change="() => change(formInline[item.param], item.param)"
clearable
></el-input>
<!-- 输入框 -->
<!-- 日期范围选择框 -->
<el-date-picker
v-if="
item.type == 'daterange' ||
item.type == 'datetimerange' ||
item.type == 'date' ||
item.type == 'datetime' ||
item.type == 'dates'
"
v-model="formInline[item.param]"
:value-format="item.valueFormat || 'MMM DD, YYYY'"
:format="item.format || 'YYYY-MM-DD HH:mm:ss'"
clearable
:type="item.type || ''"
:range-separator="item.rangeSeparator || '至'"
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
:placeholder="item.placeholder || '请选择' + item.label"
@clear="() => clear(formInline[item.param], item.param)"
@change="() => change(formInline[item.param], item.param)"
style="width: 100%"
>
</el-date-picker>
<!-- 日期范围选择框end -->
<!-- 级联选择器 -->
<el-cascader
v-if="item.type == 'cascader'"
v-model="formInline[item.param]"
:options="item.options"
:props="item.props"
:placeholder="item.placeholder || '请选择' + item.label"
clearable
@clear="() => clear(formInline[item.param], item.param)"
@change="() => change(formInline[item.param], item.param)"
></el-cascader>
<!-- 级联选择器end -->
</el-form-item>
</el-col>
<slot name="formItem"></slot>
</el-row>
</div>
<div class="top-screen-right">
<div v-if="isShowDefault">
<el-button @click="resetForm" class="top-right-bottom top-right-Reset-bottom top-right-bottom-I">重置</el-button>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button
type="primary"
style="margin-left: 0px"
text
@click="openBy = !openBy"
v-if="formItemList.map((item) => item.span || 6).reduce((a, b) => a + b) > 24"
>
{{ openBy ? '收起' : '展开' }}
<el-icon class="el-icon--right" :style="openBy ? '' : 'transform: rotate(180deg);'"><ArrowUpBold /></el-icon>
</el-button>
</div>
<!-- 可用于显示其他按钮 -->
<slot name="formButton" :item="formInline"></slot>
</div>
</el-form>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
const props = defineProps({
activeIcon: {
type: String,
},
emitSearch: {
// 是否立即执行搜索
type: Boolean,
default: false,
},
isShowDefault: {
// 是否展示默认按钮
default: true,
},
isShowLabel: {
// 是否展示默认按钮
default: true,
},
formItemList: {
type: Array,
default() {
return [
{
label: '下拉框',
type: 'select',
selectOptions: [{ text: 111, value: 111 }],
param: 'company',
defaultSelect: '', // 下拉框默认选中项
multiple: false,
labelKey: 'text', // 可选自定义下拉框label字段
valueKey: 'value', // 可选自定义下拉框value字段
},
{
label: '输入框',
type: 'input',
param: 'name',
},
{
label: '日期范围',
type: 'daterange',
param: 'createtime',
},
{
label: '级联选择器',
type: 'cascader',
param: 'cascader',
options: [],
props: { multiple: false },
},
];
},
},
});
const emit = defineEmits(['change', 'search']);
const formInline = reactive({});
const openBy = ref(false);
const ruleForm = ref(null);
onMounted(() => {
for (const obj of props.formItemList) {
formInline[obj.param] = obj.defaultSelect || null;
}
});
watch(
() => props.emitSearch,
(newVal, oldVal) => {
if (newVal) {
console.log('此时触发--立即执行搜索');
emit('search', formInline);
}
},
);
watch(
() => props.formItemList,
(newVal, oldVal) => {
for (const obj of newVal) {
if (obj.defaultSelect) {
formInline[obj.param] = obj.defaultSelect;
}
}
},
{ deep: true },
);
// input清除事件
const clear = (e, param) => {
if (e === '') {
formInline[param] = null;
}
};
// 清除指定内容
const clearYou = (type) => {
formInline[type] = null;
};
// input change事件
const change = (e, param) => {
emit('change', { e, param });
};
const onSubmit = () => {
// console.log('submit!',formInline);
emit('search', formInline);
};
const clearIn = (param) => {
const formInlineItem = {};
formInlineItem[param] = null; // 指定筛选条件清空
// formInline = formInlineItem;
};
// 添加默认值
const setDefaultData = (param) => {
for (const iterator in param) {
formInline[iterator] = param[iterator];
}
};
// 当导出不需要执行搜索事件时
const penetrateParams = (param) => {
return formInline;
};
const resetForm = () => {
ruleForm.value.resetFields();
const formInlineItem = {};
for (const obj of props.formItemList) {
// formInline[obj.param] = obj.defaultSelect || ""; // 重置时下拉框的默认值如果要保留就选用这个
formInline[obj.param] = null; // 所有筛选条件清空
}
emit('search', formInline);
};
// 暴露子组件方法
defineExpose({
penetrateParams,
});
</script>
<style scoped lang="scss">
.dialog-search {
margin-bottom: 10px;
}
el-form-item--large asterisk-left .top-title {
font-size: 20px;
font-family: RZRXNFHJ;
font-weight: 500;
color: #1f2329;
}
.top-screen {
background-color: #fff;
margin: 0px 0 0 0;
padding: 12px 16px;
padding-bottom: 0px;
border-radius: 2px;
display: flex;
justify-content: space-between;
.top-screen-left {
flex: 1;
}
.ruleForm_left {
flex: 1;
overflow: hidden;
transition: all 0.3s;
}
}
.el-icon--right {
transition: all 0.3s;
}
.top-screen-right {
> div {
width: 240px;
}
}
.top-right-bottom {
background: --el-color-primary;
border-radius: 2px 2px 2px 2px;
font-size: 14px;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #ffffff;
line-height: 22px;
padding: 4px 16px;
margin-left: 12px;
}
.top-right-Reset-bottom {
color: #1f2329;
background-color: #fff;
border: 1px solid #c9cdd0;
}
::v-deep(.el-form) {
width: 100%;
}
::v-deep(.el-form-item__content) {
width: 100%;
}
::v-deep(.el-form-item) {
width: 100%;
}
</style>

View File

@@ -0,0 +1,256 @@
<template>
<!-- 1.先判断是否常用类型
2. 特殊列columned = true 插槽名称同展示字段名 -->
<!-- ref="multipleTable" //获取元素的标识
:height="settings.height"//表格自定义高度
v-loading="settings.isLoading"//数据加载动画
@selection-change="(e) => handleClick('select', e)"//当选择项发生变化时会触发该事件
highlight-current-row//高亮行
@current-change="handleCurrentChange"//行发生变化时出发
@cell-click="handleCellclick"//当某个单元格被点击时会触发该事件
@select="handleSelect"//有勾选框单选
@select-all="handleSelectall"//有勾选框全选
:data="data.list"//表格展示数据
style="width: 100%"//表格宽度
row-key="id"//折叠表格必填项,行标识
:expand-row-keys="settings.expandrowkeys"//需要展开行的数组
:indent="0"//展开行缩紧
default-expand-all//默认关闭展开行
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"//展开行设置有children默认就有展开行 -->
<div class="eltable">
<el-table
ref="multipleTable"
:max-height="props.settings.height"
v-loading="props.settings.isLoading"
@selection-change="(e) => handleClick('select', e)"
highlight-current-row
@current-change="handleCurrentChange"
@cell-click="handleCellclick"
@select="handleSelect"
@select-all="handleSelectall"
:data="props.tableData"
style="width: 100%"
:border="props.settings.isBorder"
:indent="32"
:default-expand-all="props.settings.defaultExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:reserve-selection="props.settings.isSelection"
>
<!-- //固定勾选框列按需设置settings -->
<el-table-column v-if="props.settings.isSelection" width="55" type="selection" fixed></el-table-column>
<!-- //固定排序列按需设置settings -->
<el-table-column v-if="props.settings.isIndex" type="index" :index="1" fixed label="序号" width="60"></el-table-column>
<!-- //默认数据展示 -->
<template v-for="item in props.header">
<template v-if="item.prop !== 'action' && !item.columned">
<!-- type //selection/index/expand -->
<el-table-column
:key="item.prop"
:type="item.type"
:prop="item.prop"
:label="item.label"
:width="item.width"
:fixed="item.fixed"
:show-overflow-tooltip="true"
header-align="left"
class-name="cloumnCell"
></el-table-column>
</template>
<slot v-if="item.columned" :name="item.prop"></slot>
</template>
<!-- //操作列之类的插槽 -->
<slot name="action"></slot>
</el-table>
<!-- //翻页组件封装 -->
<el-pagination
v-if="props.settings.isPagination"
background
style="display: flex; justify-content: flex-end; padding: 25px 25px 25px 0; background: #fff; margin-top: 10px"
@size-change="(e) => handleClick('pageSize', e)"
@current-change="(e) => handleClick('currentPage', e)"
:current-page="currentPage"
:page-sizes="[20, 50, 100, 200]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="props.settings.total"
></el-pagination>
</div>
</template>
<script setup>
/* 传值说明:
settings:{ 相关配置
isLoading加载数据时显示动效
height表格高度
autoHeight:true 是否自动高度
isSelection; 是否有多选
selectionWidth多选的宽度
isIndex 是否需要序列号,默认不需要
isBorder:是否加框线,默认添加,
isPagination:是否添加分页,默认false,
total: 列表总条数
currentPage 当前页数
}
tableData: { 表格数据}
header:{表头字段
columned 是否使用插槽
}
事件说明:
handleClick
参数 type 点击的类型,如
选择select, 多选时
编辑edit, 按钮为编辑时
查看show, 按钮为查看时
删除delete, 按钮为删除时
当前条数pageSize, 开启分页时
当前页数currentPage等 开启分页时
e 选中项
i 选中索引
*/
import { ref, reactive, onMounted, watch, computed, useAttrs } from 'vue';
const $attrs = useAttrs();
const emit = defineEmits(['select', 'pageSize', 'currentPage', 'currentChange', 'handleSelect', 'handleSelectall']);
const props = defineProps({
tableData: {
type: Array,
default: () => {
return [];
},
},
header: { type: Array, required: true },
settings: {
type: Object,
default: () => {
return {
height: null,
isBorder: true,
isLoading: false,
isIndex: false,
isSelection: false,
isPagination: false,
currentPage: 1,
pageSize: 20,
total: 0,
defaultExpandAll: false,
};
},
},
});
const multipleTable = ref(null);
watch(
props.settings,
(e) => {
if (typeof e.isBorder === 'undefined') e.isBorder = true;
if (typeof e.total === 'undefined') e.total = 0;
},
{ immediate: true },
);
// watch(() => props.settings., () => {
// debugger
// props.tableData.map((item) => {
// if (props.settings.expandrowkeys[0] != item.id) {
// props.$refs.multipleTable.toggleRowExpansion(item, false);
// } else {
// props.$refs.multipleTable.toggleRowExpansion(item);
// }
// });
// })
const toggleSelection = (rows) => {
if (rows) {
rows.forEach((row) => {
multipleTable.value.toggleRowSelection(row);
});
} else {
multipleTable.value.clearSelection();
}
};
const handleClick = (type, e) => {
console.log(e);
if (type == 'select') {
emit('select', e);
} else if (type == 'pageSize') {
// 一页多少条
emit('pageSize', e);
} else {
// 第几页
emit('currentPage', e);
}
};
const handleCurrentChange = (val) => {
emit('currentChange', val);
};
// handleCellclick(row, column, cell, event) {//下面函数可包含参数
const handleCellclick = () => {
// 切换选中状态
// this.$refs.multipleTable.toggleRowSelection(row);
};
const handleSelect = (selection) => {
// 勾选
emit('handleSelect', selection);
};
const handleSelectall = (selection) => {
// 全选
emit('handleSelectall', selection);
};
</script>
<style scoped lang="less">
::v-deep(.el-table--border .el-table__cell, .el-table__body-wrapper .el-table--border.is-scrolling-left ~ .el-table__fixed) {
border-right: 0 !important;
}
::v-deep(.el-table--border, .el-table--group) {
border: 0 !important;
height: 50%;
overflow-y: auto;
border-right: 0 !important;
}
::v-deep(.el-table--border::after, .el-table--group::after, .el-table::before) {
background-color: transparent !important;
}
::v-deep(.el-table thead) {
font-size: 14px;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 900;
color: #1d2129;
}
::v-deep(.el-table__fixed-right::before, .el-table__fixed::before) {
height: 0px !important;
}
/* //关闭原生组件展开行按钮 */
.el-table .el-table__expand-icon {
display: none;
}
/* //展开行样式单独设置 */
.el-table__row--level-1 {
background-color: #f0edff;
}
/* //取消点击行配置 */
.el-table__body tr.current-row > td {
background-color: #fff !important;
}
::v-deep(.el-table thead) {
font-size: 14px;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 900;
color: #1d2129;
}
::v-deep(.el-table .cell) {
font-size: 13px;
}
.eltable {
background: white;
padding: 10px 10px;
}
::v-deep(.is-leaf) {
/* background: #f7f9fb !important; */
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<el-select
v-model="selectedValue"
:placeholder="placeholder"
style="width: 240px"
:filterable="filterable"
:remote="remote"
:remote-method="remoteMethod"
:clearable="clearable"
@change="handleChange"
>
<el-option v-for="item in deviceOptions" :key="item.orgId" :label="item.mobile" :value="item.orgId" />
<el-pagination
v-if="pagination"
small
style="padding: 0px 20px"
@current-change="handlePageChange"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
/>
</el-select>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue';
import { queryListOps } from '@/api/farmOps';
const emits = defineEmits(['selected-value-change']);
const props = defineProps({
cid: { type: Number, default: '' },
placeholder: { type: String, default: '请选择养殖户账号' },
filterable: { type: Boolean, default: true },
remote: { type: Boolean, default: true },
clearable: { type: Boolean, default: true },
pagination: { type: Boolean, default: true },
name: { type: String, default: '' },
managerName: { type: String, default: '' },
});
const selectedValue = ref('');
const deviceOptions = ref([]);
const currentPage = ref(1);
const pageSize = ref(20);
const total = ref(0);
const loading = ref(false);
// 远程搜索方法
const remoteMethod = (query) => {
if (query !== '') {
loading.value = true;
const params = { mobile: query, pageNum: 1, pageSize: pageSize.value };
queryListOps(params).then((ret) => {
loading.value = false;
total.value = ret.data.total;
deviceOptions.value = ret.data.rows;
});
} else {
getOptions();
}
};
// 分页切换页码方法
const handlePageChange = (val) => {
currentPage.value = val;
getOptions();
};
// 选项变化时的回调方法
const handleChange = (val) => {
if (val) {
const row = deviceOptions.value.find((item) => item.orgId === val);
if (row) {
emits('update:name', row.name);
emits('update:managerName', row.managerName);
emits('update:cid', val);
} else {
emits('update:name', '');
emits('update:managerName', '');
emits('update:cid', '');
}
} else {
emits('update:name', '');
emits('update:managerName', '');
emits('update:cid', '');
}
};
const getOptions = () => {
const params = { pageNum: currentPage.value, pageSize: pageSize.value };
queryListOps(params).then((ret) => {
deviceOptions.value = ret.data.rows;
total.value = ret.data.total;
});
};
// 方法:根据 ID 查找并设置选中的值
// const setSelectedValueById = async (id) => {
// const params = { id: id, pageNum: 1, pageSize: 1 };
// const { data } = await queryListOps(params);
// if (data.rows.length > 0) {
// selectedValue.value = data.rows[0].id;
// } else {
// selectedValue.value = '';
// }
// };
onMounted(() => {
getOptions();
});
</script>

View File

@@ -0,0 +1,40 @@
<template>
<component :is="type" v-bind="linkProps()">
<slot />
</component>
</template>
<script setup>
import { isExternal } from '@/utils/validate';
const props = defineProps({
to: {
type: [String, Object],
required: true,
},
});
const isExt = computed(() => {
return isExternal(props.to);
});
const type = computed(() => {
if (isExt.value) {
return 'a';
}
return 'router-link';
});
function linkProps() {
if (isExt.value) {
return {
href: props.to,
target: '_blank',
rel: 'noopener',
};
}
return {
to: props.to,
};
}
</script>

View File

@@ -0,0 +1,102 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :class="{ 'submenu-title-noDropdown': !isNest }" :index="resolvePath(onlyOneChild.path)">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title
><span :title="hasTitle(onlyOneChild.meta.title)" class="menu-title">{{ onlyOneChild.meta.title }}</span></template
>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta && item.meta.icon" />
<span :title="hasTitle(item.meta.title)" class="menu-title">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="(child, index) in item.children"
:key="child.path + index"
:base-path="resolvePath(child.path)"
:is-nest="true"
:item="child"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup>
import { isExternal } from '@/utils/validate';
import AppLink from './Link.vue';
import { getNormalPath } from '@/utils/aiotagro.js';
const props = defineProps({
// route object
item: {
type: Object,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
basePath: {
type: String,
default: '',
},
});
const onlyOneChild = ref({});
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
}
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false;
}
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item;
return true;
});
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true;
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
return true;
}
return false;
}
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
return routePath;
}
if (isExternal(props.basePath)) {
return props.basePath;
}
if (routeQuery) {
const query = JSON.parse(routeQuery);
return { path: getNormalPath(`${props.basePath}/${routePath}`), query };
}
return getNormalPath(`${props.basePath}/${routePath}`);
}
function hasTitle(title) {
if (title.length > 5) {
return title;
}
return '';
}
</script>

View File

@@ -0,0 +1,121 @@
<template>
<section id="webAdminLayoutHeader" class="h-full flex items-center header-menu">
<div class="flex-1">
<el-breadcrumb :separator-icon="ArrowRight">
<!-- <el-breadcrumb-item :to="{ path: '/' }"> </el-breadcrumb-item> -->
<el-breadcrumb-item v-if="webTitle !== '首页'">{{ webTitle }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="flex">
<div style="margin-right: 20px" v-if="userType == 1">
<el-button type="primary">
<a style="color: #fff" href="https://b.datav.run/share/page/7d743ac7c27c0d332d0a19e758e0d7c4" target="_blank"> 可视化大屏 </a>
</el-button>
</div>
<div class="headImg">
<img alt="" src="../../../assets/photo.jpg" />
</div>
<div class="headName">{{ username }}</div>
<em style="font-style: normal; position: relative; top: 2px">|</em>
<div class="loginOut" @click="loginOut">退出</div>
</div>
</section>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue';
import { ArrowRight } from '@element-plus/icons-vue';
import { useRouter, useRoute } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useUserStore } from '~/store/user';
import { loginoutApi } from '~/api/sys.js';
import { removeToken } from '~/utils/auth.js';
import usePermissionStore from '@/store/permission.js';
const permissionStore = usePermissionStore();
const router = useRouter();
const route = useRoute();
const userStore = useUserStore();
const userType = ref(userStore.userType);
const username = ref(userStore.username);
const webTitle = ref('');
onMounted(() => {
webTitle.value = route.meta.title;
});
watch(
() => route.path,
() => {
webTitle.value = route.meta.title;
},
{
immediate: true,
deep: true,
},
);
const loginOut = () => {
ElMessageBox.confirm('您确定要退出吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
// router.push('/login');
loginoutApi().then((ret) => {
userStore.updateToken('');
userStore.updateUserName('');
userStore.updateLoginUser(null);
permissionStore.setRoutes([]);
permissionStore.setSidebarRouters([]);
removeToken();
ElMessage({ type: 'success', message: '已经退出!' });
router.push('/login');
});
})
.catch(() => {
ElMessage({ type: 'info', message: '取消退出!' });
});
};
</script>
<style scoped>
.el-breadcrumb {
color: #000;
}
.headImg {
color: #666;
margin-right: 20px;
}
.headImg img {
margin: auto;
width: 30px;
height: 30px;
border-radius: 50%;
}
.headName {
font-family: MicrosoftYaHei;
font-size: 14px;
color: #666666;
letter-spacing: 0.17px;
display: inline-block;
line-height: 30px;
padding-right: 20px;
}
.loginOut {
margin-left: 20px;
color: #3b74ff;
font-size: 12px;
justify-content: center;
align-items: center;
display: inline-block;
line-height: 30px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<section id="webAdminLayoutLeftMenu" class="w-full bg-white left-menu">
<h1 class="bg-[#21376b] text-white text-center mt-5" style="font-size: 24px; font-weight: bold; display: flex; justify-content: center">
<!-- <img class="login-img" src="@/assets/images/hglogo.png" alt="logo" /> -->
<div>牛只运输跟踪系统</div>
</h1>
<div class="menu-list">
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:collapse="isCollapse"
:default-active="currentMenu"
:router="true"
:unique-opened="true"
background-color="#21376b"
class="border-none left-menu-list"
text-color="#fff"
>
<template v-for="route in sidebarRouters">
<template v-if="route.children && route.children.length > 0">
<el-sub-menu :index="route.path">
<template #title>
<svg-icon :icon-class="route.meta.icon" />
<span class="pl-3">{{ route.meta.title }}</span>
</template>
<template v-for="cItem in route.children">
<template v-if="cItem.children && cItem.children.length > 0">
<el-sub-menu :index="route.path + '/' + cItem.path">
<template #title>
<!-- <svg-icon :icon-class="cItem.meta.icon" /> -->
<span class="pl-3">{{ cItem.meta.title }}</span>
</template>
<template v-for="subItem in cItem.children">
<el-menu-item :index="route.path + '/' + cItem.path + '/' + subItem.path">
<svg-icon :icon-class="subItem.meta.icon" />
<span class="pl-3">{{ subItem.meta.title }}</span>
</el-menu-item>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="route.path + '/' + cItem.path">
<svg-icon :icon-class="cItem.meta.icon" />
<span class="pl-3">{{ cItem.meta.title }}</span>
</el-menu-item>
</template>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="route.path">
<svg-icon :icon-class="route.meta.icon" />
<span class="pl-3">{{ route.meta.title }}</span>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
</section>
</template>
<script setup>
import { computed, ref, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import usePermissionStore from '@/store/permission.js';
import { getConfig } from '~/api/sys.js';
const permissionStore = usePermissionStore();
const data = reactive({
name: '',
logoUrl: '',
});
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const route = useRoute();
const currentMenu = ref('/');
const isCollapse = ref(false);
const userStore = JSON.parse(localStorage.getItem('userStore'));
watch(
() => route,
(nVal) => {
currentMenu.value = nVal.path;
},
{
immediate: true,
deep: true,
},
);
const getDataList = () => {
getConfig().then((ret) => {
if (ret.data) {
data.name = ret.data.name;
if (ret.data.logoUrl) {
data.logoUrl = ret.data.logoUrl;
}
}
});
};
const activeMenu = computed(() => {
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
});
onMounted(() => {
// getDataList();
});
</script>
<style scoped>
.login-img {
margin-right: 10px;
margin-top: 5px;
/* margin: 30px auto 14px; */
width: 26px;
height: 26px;
}
.left-menu,
.left-menu-list,
.el-sub-menu,
.el-menu-item {
background: #21376b;
}
.left-menu-list:hover {
background: #041e61;
}
.el-menu-item.is-active {
color: #fff;
background: #274389;
border-right: 5px solid #3b74ff;
}
.el-menu-item {
font-family: MicrosoftYaHei;
font-size: 14px;
letter-spacing: 0.17px;
color: #fff;
}
.el-menu-item:hover {
background-color: #041e61;
}
.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title {
color: #fff;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<section id="layoutSectionIndex" class="h-full w-full flex">
<el-container>
<el-aside class="flex">
<LayoutLeftMenu />
</el-aside>
<el-container>
<el-header class="el-header">
<LayoutHeader />
</el-header>
<el-main class="container-main">
<div class="p-2 h-full flex flex-col mainSectionsContainer">
<RouterView class="" />
</div>
</el-main>
</el-container>
</el-container>
</section>
</template>
<script setup>
import LayoutHeader from './component/header.vue';
import LayoutLeftMenu from './component/left-menu.vue';
</script>
<style scoped>
.el-container {
background: #f5f6fa;
}
.el-header {
@apply shadow-2xl;
background: #fff;
}
.container-main {
height: 100%;
background: #f5f6fa;
color: #333333;
// 看一下吧
}
div.mainSectionsContainer {
> section {
@apply flex flex-col h-full;
::v-deep(div.global-search-container) {
height: auto;
}
::v-deep(div.main-container) {
@apply flex flex-col;
flex: 1;
::v-deep(div) {
flex: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div style="width: 100%; height: 800px">
<baidu-map class="bm-view" :center="{ lng: state.lng, lat: state.lat }" :zoom="15" :scroll-wheel-zoom="true" v="3.0" type="API">
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
<!-- :icon="{ url: 'http://api.map.baidu.com/img/markers.png', size: { width: 108, height: 108 } }" -->
<bm-marker :position="state" :raiseOnDrag="true">
<bm-info-window :show="show" @close="show = false" @open="show = true">
{{ address }}
</bm-info-window>
</bm-marker>
</baidu-map>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue';
import { BaiduMap, BmMapType, BmMarker, BmInfoWindow } from 'vue-baidu-map-3x';
const props = defineProps({
longitude: {
type: Number,
default: [],
required: true,
},
latitude: {
type: Number,
default: [],
required: true,
},
});
const show = ref(false);
const address = ref('undefined');
const state = reactive({
lng: '',
lat: '',
});
watch(
() => props.longitude,
(val) => {
state.lng = val;
},
{
immediate: true,
},
);
watch(
() => props.latitude,
(val) => {
state.lat = val;
},
{
immediate: true,
},
);
const handelClick = (e) => {
show.value = false;
state.lng = e.point.lng;
state.lat = e.point.lat;
const geocoder = new window.BMap.Geocoder();
// 解析地址
geocoder.getLocation(e.point, (res) => {
if (res) {
show.value = true;
address.value = res.address;
}
});
};
</script>
<style scoped>
/* 去除水印 */
::v-deep(.BMap_cpyCtrl) {
display: none;
}
::v-deep(.anchorBL) {
display: none !important;
}
/* 布局css元素 */
.bm-view {
width: 100%;
height: 100%;
margin: auto;
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div>
<!-- {{ userStore.name }} - {{ userStore.age }} - {{ userStore.sex }} -->
<br />
<button @click="updateUserName">修改值</button>
<br />
<button @click="updateOldUserName">修改回原值</button>
</div>
</template>
<script setup>
import { useUserStore } from '~/store/user';
const userStore = useUserStore();
// 打印useUserStore里 state的信息
console.log(userStore.$state);
const updateUserName = () => {
userStore.updateUserName('嗨!');
};
const updateOldUserName = () => {
userStore.updateUserName('李四');
};
</script>