添加新的需求
This commit is contained in:
122
pc-cattle-transportation/src/components/Editor/index.vue
Normal file
122
pc-cattle-transportation/src/components/Editor/index.vue
Normal 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>
|
||||
115
pc-cattle-transportation/src/components/IconSelect/index.vue
Normal file
115
pc-cattle-transportation/src/components/IconSelect/index.vue
Normal 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>
|
||||
@@ -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;
|
||||
332
pc-cattle-transportation/src/components/ImageUpload/index.vue
Normal file
332
pc-cattle-transportation/src/components/ImageUpload/index.vue
Normal 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>
|
||||
19
pc-cattle-transportation/src/components/InnerLink/index.vue
Normal file
19
pc-cattle-transportation/src/components/InnerLink/index.vue
Normal 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>
|
||||
81
pc-cattle-transportation/src/components/NumberCounter.vue
Normal file
81
pc-cattle-transportation/src/components/NumberCounter.vue
Normal 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>
|
||||
105
pc-cattle-transportation/src/components/Pagination/index.vue
Normal file
105
pc-cattle-transportation/src/components/Pagination/index.vue
Normal 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>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -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>
|
||||
55
pc-cattle-transportation/src/components/SvgIcon/index.vue
Normal file
55
pc-cattle-transportation/src/components/SvgIcon/index.vue
Normal 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>
|
||||
10
pc-cattle-transportation/src/components/SvgIcon/svgicon.js
Normal file
10
pc-cattle-transportation/src/components/SvgIcon/svgicon.js
Normal 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);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
59
pc-cattle-transportation/src/components/layout/index.vue
Normal file
59
pc-cattle-transportation/src/components/layout/index.vue
Normal 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>
|
||||
88
pc-cattle-transportation/src/components/map/index.vue
Normal file
88
pc-cattle-transportation/src/components/map/index.vue
Normal 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>
|
||||
24
pc-cattle-transportation/src/components/usePinia.vue
Normal file
24
pc-cattle-transportation/src/components/usePinia.vue
Normal 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>
|
||||
Reference in New Issue
Block a user