APP文件新增
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<wd-popup v-model="show" position="bottom" @close="handleClose">
|
||||
<view class="contetn">
|
||||
<wd-text custom-class="title" v-if="title" :text="title"></wd-text>
|
||||
<wd-cell-group border>
|
||||
<wd-cell
|
||||
v-for="(item, index) in options"
|
||||
:icon="item.icon"
|
||||
:label="item.label"
|
||||
:custom-class="item.color"
|
||||
clickable
|
||||
@click="handleClick(item)"
|
||||
></wd-cell>
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
defineOptions({
|
||||
name: 'BottomOperate',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const eimt = defineEmits(['change', 'close'])
|
||||
const show = ref(true)
|
||||
const props = defineProps(['title', 'data', 'options'])
|
||||
const handleClose = () => {
|
||||
show.value = false
|
||||
setTimeout(() => {
|
||||
eimt('close')
|
||||
}, 300)
|
||||
}
|
||||
const handleClick = (item) => {
|
||||
eimt('change', { option: item, data: props.data })
|
||||
handleClose()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contetn {
|
||||
padding-top: 10px;
|
||||
:deep(.title) {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.wd-cell) {
|
||||
padding-left: 10px;
|
||||
--wot-cell-icon-size: 15px;
|
||||
--wot-cell-label-color: #444;
|
||||
--wot-cell-label-fs: 14px;
|
||||
.wd-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.wd-cell__label {
|
||||
margin-top: 0;
|
||||
}
|
||||
&.red {
|
||||
color: red;
|
||||
--wot-cell-label-color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
JeecgUniapp-master/src/components/Breadcrumb/Breadcrumb.vue
Normal file
69
JeecgUniapp-master/src/components/Breadcrumb/Breadcrumb.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="breadcrumb">
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<span
|
||||
class="breadcrumb-item"
|
||||
:class="{ 'is-link': index < items.length - 1 }"
|
||||
@click="handleClick(item, index)"
|
||||
>
|
||||
<slot :name="'item-' + index" :item="item">
|
||||
{{ item.title }}
|
||||
</slot>
|
||||
</span>
|
||||
<span v-if="index < items.length - 1" class="breadcrumb-separator">{{ separator }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Breadcrumb',
|
||||
})
|
||||
|
||||
interface BreadcrumbItem {
|
||||
title: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array as PropType<BreadcrumbItem[]>,
|
||||
default: () => [],
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
default: '/',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
const handleClick = (item: BreadcrumbItem, index: number) => {
|
||||
if (index < props.items.length - 1) {
|
||||
emit('click', item, index)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
&-item {
|
||||
&.is-link {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
&:last-child {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
&-separator {
|
||||
margin: 0 8px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<view class="CategorySelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" position="bottom" v-model="popupShow">
|
||||
<view class="content">
|
||||
<view class="operation">
|
||||
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
|
||||
<view class="confrim" @click.stop="confirm">确定</view>
|
||||
</view>
|
||||
<scroll-view class="flex-1" scroll-y>
|
||||
<DaTree
|
||||
:data="treeData"
|
||||
labelField="title"
|
||||
valueField="key"
|
||||
loadMode
|
||||
:showCheckbox="multiple"
|
||||
:showRadioIcon="false"
|
||||
:checkStrictly="true"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
:loadApi="asyncLoadTreeData"
|
||||
@change="handleTreeChange"
|
||||
></DaTree>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import DaTree from '@/uni_modules/da-tree/index.vue'
|
||||
import { isArray } from '@/utils/is'
|
||||
defineOptions({
|
||||
name: 'CategorySelect',
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pid: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
pcode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const toast = useToast()
|
||||
const api = {
|
||||
loadDictItem: '/sys/category/loadDictItem/',
|
||||
loadTreeData: '/sys/category/loadTreeData',
|
||||
}
|
||||
const showText = ref('')
|
||||
const popupShow = ref(false)
|
||||
const treeData = ref<any[]>([])
|
||||
const treeValue = ref([])
|
||||
const attrs = useAttrs()
|
||||
const defaultCheckedKeys: any = ref([])
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
popupShow.value = true
|
||||
}
|
||||
}
|
||||
const cancel = () => {
|
||||
popupShow.value = false
|
||||
}
|
||||
const confirm = () => {
|
||||
const titles = treeValue.value.map((item) => item.title)
|
||||
const keys = treeValue.value.map((item) => item.key).join(',')
|
||||
showText.value = titles.join(',')
|
||||
popupShow.value = false
|
||||
emit('update:modelValue', keys)
|
||||
emit('change', keys)
|
||||
}
|
||||
const handleTreeChange = (value, record) => {
|
||||
const { originItem, checkedStatus } = record
|
||||
const { key, title } = originItem
|
||||
if (checkedStatus) {
|
||||
// 选中
|
||||
if (props.multiple) {
|
||||
treeValue.value.push({ key, title })
|
||||
} else {
|
||||
treeValue.value = [{ key, title }]
|
||||
}
|
||||
} else {
|
||||
// 取消
|
||||
if (props.multiple) {
|
||||
const findIndex = treeValue.value.findIndex((item) => item.key == key)
|
||||
if (findIndex != -1) {
|
||||
treeValue.value.splice(findIndex, 1)
|
||||
}
|
||||
} else {
|
||||
treeValue.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
const transformField = (result) => {
|
||||
for (let i of result) {
|
||||
i.value = i.key
|
||||
if (i.leaf == false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf == true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// 异步加载
|
||||
const asyncLoadTreeData = ({ originItem }) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
let param = {
|
||||
pid: originItem.key,
|
||||
condition: props.condition,
|
||||
}
|
||||
http
|
||||
.get(api.loadTreeData, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
transformField(result)
|
||||
resolve(result)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
.catch((err) => resolve(null))
|
||||
})
|
||||
}
|
||||
// 加载根节点
|
||||
function loadRoot() {
|
||||
let param = {
|
||||
pid: props.pid,
|
||||
pcode: !props.pcode ? '0' : props.pcode,
|
||||
condition: props.condition,
|
||||
}
|
||||
http
|
||||
.get(api.loadTreeData, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
if (result && result.length > 0) {
|
||||
transformField(result)
|
||||
treeData.value = result
|
||||
}
|
||||
} else {
|
||||
toast.warning('分类字典书组件根节点数据加载失败~')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.warning('分类字典书组件根节点数据加载失败~')
|
||||
})
|
||||
}
|
||||
// 翻译input内的值
|
||||
function loadItemByCode() {
|
||||
let value = props.modelValue
|
||||
if(value){
|
||||
if (isArray(props.modelValue)) {
|
||||
// @ts-ignore
|
||||
value = value.join()
|
||||
}
|
||||
if (value === treeData.value.map((item) => item.key).join(',')) {
|
||||
// 说明是刚选完,内部已有翻译。不需要再请求
|
||||
return
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(api.loadDictItem, { ids: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.join(',')
|
||||
defaultCheckedKeys.value = value
|
||||
} else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
loadItemByCode()
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
watch(
|
||||
() => props.pcode,
|
||||
() => {
|
||||
loadRoot()
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-popup-wrapper) {
|
||||
.wd-popup {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
height: 50vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 40px;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 1px;
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.cancel,
|
||||
.confrim {
|
||||
font-size: 15px;
|
||||
height: 40px;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.confrim {
|
||||
color: var(--wot-color-theme);
|
||||
}
|
||||
}
|
||||
:deep(.da-tree) {
|
||||
.da-tree-item__checkbox {
|
||||
// display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
341
JeecgUniapp-master/src/components/DateTime/DateTime.vue
Normal file
341
JeecgUniapp-master/src/components/DateTime/DateTime.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class="DateTime">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" position="bottom" v-model="popupShow">
|
||||
<view class="content">
|
||||
<view class="operation">
|
||||
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
|
||||
<view class="confrim" @click.stop="confirm">确定</view>
|
||||
</view>
|
||||
<picker-view class="picker-view" :value="pickerValue" @change="handleChange">
|
||||
<!-- 年 -->
|
||||
<picker-view-column v-if="showYear">
|
||||
<view class="picker-item" v-for="(item, index) in years" :key="index">
|
||||
{{ item }}年
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 月 -->
|
||||
<picker-view-column v-if="showMonth">
|
||||
<view class="picker-item" v-for="(item, index) in months" :key="index">
|
||||
{{ item }}月
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 日 -->
|
||||
<picker-view-column v-if="showDay">
|
||||
<view class="picker-item" v-for="(item, index) in days" :key="index">{{ item }}日</view>
|
||||
</picker-view-column>
|
||||
<!-- 时 -->
|
||||
<picker-view-column v-if="showHour">
|
||||
<view class="picker-item" v-for="(item, index) in hours" :key="index">
|
||||
{{ item }}时
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 分 -->
|
||||
<picker-view-column v-if="showMinute">
|
||||
<view class="picker-item" v-for="(item, index) in minutes" :key="index">
|
||||
{{ item }}分
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 秒 -->
|
||||
<picker-view-column v-if="showSecond">
|
||||
<view class="picker-item" v-for="(item, index) in seconds" :key="index">
|
||||
{{ item }}秒
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import dayjs from 'dayjs'
|
||||
import { getPlaceholder } from '@/common/uitls'
|
||||
|
||||
defineOptions({
|
||||
name: 'DateTime',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 显示格式:year/month/day/hour/minute/second
|
||||
format: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
// 开始时间
|
||||
startTime: {
|
||||
type: String,
|
||||
default: '1970-01-01 00:00:00',
|
||||
},
|
||||
// 结束时间
|
||||
endTime: {
|
||||
type: String,
|
||||
default: '2100-12-31 23:59:59',
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const toast = useToast()
|
||||
|
||||
// 显示控制
|
||||
const showYear = computed(() => props.format.includes('YYYY'))
|
||||
const showMonth = computed(() => props.format.includes('MM'))
|
||||
const showDay = computed(() => props.format.includes('DD'))
|
||||
const showHour = computed(() => props.format.includes('HH'))
|
||||
const showMinute = computed(() => props.format.includes('mm'))
|
||||
const showSecond = computed(() => props.format.includes('ss'))
|
||||
|
||||
// 数据源
|
||||
const years = ref<number[]>([])
|
||||
const months = ref<number[]>([])
|
||||
const days = ref<number[]>([])
|
||||
const hours = ref<number[]>([])
|
||||
const minutes = ref<number[]>([])
|
||||
const seconds = ref<number[]>([])
|
||||
|
||||
// 当前选中的值
|
||||
const pickerValue = ref<number[]>([])
|
||||
const showText = computed(() => props.modelValue)
|
||||
const popupShow = ref(false)
|
||||
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
const start = dayjs(props.startTime)
|
||||
const end = dayjs(props.endTime)
|
||||
|
||||
// 初始化年份
|
||||
if (showYear.value) {
|
||||
years.value = Array.from({ length: end.year() - start.year() + 1 }, (_, i) => start.year() + i)
|
||||
}
|
||||
|
||||
// 初始化月份
|
||||
if (showMonth.value) {
|
||||
months.value = Array.from({ length: 12 }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化日期
|
||||
if (showDay.value) {
|
||||
const currentYear = props.modelValue ? dayjs(props.modelValue).year() : start.year()
|
||||
const currentMonth = props.modelValue ? dayjs(props.modelValue).month() + 1 : 1
|
||||
const daysInMonth = dayjs(`${currentYear}-${currentMonth}`).daysInMonth()
|
||||
days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化小时
|
||||
if (showHour.value) {
|
||||
hours.value = Array.from({ length: 24 }, (_, i) => i)
|
||||
}
|
||||
|
||||
// 初始化分钟
|
||||
if (showMinute.value) {
|
||||
minutes.value = Array.from({ length: 60 }, (_, i) => i)
|
||||
}
|
||||
|
||||
// 初始化秒
|
||||
if (showSecond.value) {
|
||||
seconds.value = Array.from({ length: 60 }, (_, i) => i)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新日期
|
||||
const updateDays = () => {
|
||||
if (!showYear.value || !showMonth.value) return
|
||||
|
||||
const year = years.value[pickerValue.value[0] || 0]
|
||||
const month = months.value[pickerValue.value[1] || 0]
|
||||
const daysInMonth = dayjs(`${year}-${month}`).daysInMonth()
|
||||
days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化pickerValue
|
||||
const initializePickerValue = () => {
|
||||
let newPickerValue = []
|
||||
if (
|
||||
[
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'YYYY-MM-DD HH',
|
||||
'YYYY',
|
||||
'YYYY-MM',
|
||||
'YYYY-MM-DD',
|
||||
].includes(props.format)
|
||||
) {
|
||||
const dateTime = props.modelValue ? dayjs(props.modelValue) : dayjs()
|
||||
if (showYear.value) {
|
||||
const yearIndex = years.value.indexOf(dateTime.year())
|
||||
newPickerValue.push(yearIndex >= 0 ? yearIndex : 0)
|
||||
}
|
||||
|
||||
if (showMonth.value) {
|
||||
const monthIndex = months.value.indexOf(dateTime.month() + 1)
|
||||
newPickerValue.push(monthIndex >= 0 ? monthIndex : 0)
|
||||
}
|
||||
|
||||
if (showDay.value) {
|
||||
const dayIndex = days.value.indexOf(dateTime.date())
|
||||
newPickerValue.push(dayIndex >= 0 ? dayIndex : 0)
|
||||
}
|
||||
|
||||
if (showHour.value) {
|
||||
newPickerValue.push(dateTime.hour())
|
||||
}
|
||||
|
||||
if (showMinute.value) {
|
||||
newPickerValue.push(dateTime.minute())
|
||||
}
|
||||
if (showSecond.value) {
|
||||
newPickerValue.push(dateTime.second())
|
||||
}
|
||||
} else if (['HH:mm:ss', 'HH:mm', 'mm:ss', 'HH', 'mm', 'ss'].includes(props.format)) {
|
||||
const dateTime = props.modelValue
|
||||
if (dateTime) {
|
||||
newPickerValue = dateTime.split(':').map((item) => +item)
|
||||
} else {
|
||||
// 获取当前时间
|
||||
const now = dayjs()
|
||||
if (showHour.value) {
|
||||
newPickerValue.push(now.hour())
|
||||
}
|
||||
if (showMinute.value) {
|
||||
newPickerValue.push(now.minute())
|
||||
}
|
||||
if (showSecond.value) {
|
||||
newPickerValue.push(now.second())
|
||||
}
|
||||
}
|
||||
}
|
||||
pickerValue.value = newPickerValue
|
||||
}
|
||||
|
||||
// 处理选择变化
|
||||
const handleChange = (e: any) => {
|
||||
const values = e.detail.value
|
||||
pickerValue.value = values
|
||||
|
||||
// 如果选择了年月,需要更新日期
|
||||
if (showYear.value && showMonth.value && showDay.value) {
|
||||
updateDays()
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确认
|
||||
const confirm = () => {
|
||||
const values = pickerValue.value
|
||||
let selectedDate = dayjs()
|
||||
let index = 0
|
||||
|
||||
// 只设置用户选择的时间部分,未选择的保持当前时间
|
||||
if (showYear.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.year(years.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showMonth.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.month(months.value[values[index]] - 1)
|
||||
index++
|
||||
}
|
||||
if (showDay.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.date(days.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showHour.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.hour(hours.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showMinute.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.minute(minutes.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showSecond.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.second(seconds.value[values[index]])
|
||||
index++
|
||||
}
|
||||
|
||||
popupShow.value = false
|
||||
const output = selectedDate.format(props.format)
|
||||
emit('update:modelValue', output)
|
||||
emit('change', output)
|
||||
}
|
||||
|
||||
// 点击取消
|
||||
const cancel = () => {
|
||||
popupShow.value = false
|
||||
}
|
||||
|
||||
// 清空选择
|
||||
const handleClear = () => {
|
||||
emit('update:modelValue', '')
|
||||
emit('change', '')
|
||||
}
|
||||
|
||||
// 点击输入框
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
initData()
|
||||
initializePickerValue()
|
||||
popupShow.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.DateTime {
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
.picker-item {
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
.cancel,
|
||||
.confrim {
|
||||
padding: 10rpx 30rpx;
|
||||
}
|
||||
.confrim {
|
||||
color: #2979ff;
|
||||
}
|
||||
}
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
JeecgUniapp-master/src/components/Grid/Grid.vue
Normal file
100
JeecgUniapp-master/src/components/Grid/Grid.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<view
|
||||
class="wrap"
|
||||
:style="{
|
||||
'--borderColor': borderColor,
|
||||
'--imgWidth': imgWidth,
|
||||
'--imgHeight': imgHeight,
|
||||
}"
|
||||
>
|
||||
<wd-grid :clickable="clickable" :column="column">
|
||||
<template v-for="(item, index) in modelValue" :key="item[itemKey]">
|
||||
<wd-grid-item
|
||||
:custom-class="getClass(index)"
|
||||
use-icon-slot
|
||||
:text="item.text"
|
||||
@itemclick="handleClik(item, index)"
|
||||
>
|
||||
<template #icon>
|
||||
<wd-img :width="imgWidth" :height="imgHeight" :src="item.img"></wd-img>
|
||||
</template>
|
||||
</wd-grid-item>
|
||||
</template>
|
||||
</wd-grid>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
defineOptions({
|
||||
name: 'Grid',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
column: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
imgWidth: {
|
||||
type: String,
|
||||
default: '28px',
|
||||
},
|
||||
imgHeight: {
|
||||
type: String,
|
||||
default: '28px',
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(165, 165, 165, 0.1)',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['itemClik'])
|
||||
const getClass = (index) => {
|
||||
let className = ''
|
||||
if (index < props.column) {
|
||||
className = 'first-row'
|
||||
}
|
||||
if ((index + 1) % props.column == 0) {
|
||||
className += ` lastCol`
|
||||
}
|
||||
return className
|
||||
}
|
||||
const handleClik = (item, index) => {
|
||||
emit('itemClik', item, index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-grid-item) {
|
||||
box-sizing: border-box;
|
||||
border-right: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
|
||||
border-bottom: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
|
||||
&.first-row {
|
||||
border-top: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
|
||||
}
|
||||
&.lastCol {
|
||||
border-right: none;
|
||||
}
|
||||
.wd-grid-item__text {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.wd-grid-item__wrapper {
|
||||
width: var(--imgWidth, 28px) !important;
|
||||
height: var(--imgHeight, 28px) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
144
JeecgUniapp-master/src/components/ImgPreview/ImgPreview.vue
Normal file
144
JeecgUniapp-master/src/components/ImgPreview/ImgPreview.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<view class="previewImage" @tap="close">
|
||||
<view class="page" v-if="urls.length > 0">
|
||||
<text class="text">{{ current + 1 }} / {{ urls.length }}</text>
|
||||
</view>
|
||||
<swiper
|
||||
class="swiper"
|
||||
:current="current"
|
||||
@change="swiperChange"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
>
|
||||
<swiper-item class="swiperItem" v-for="(item, index) in urls" :key="index">
|
||||
<movable-area class="movable-area" scale-area>
|
||||
<movable-view
|
||||
class="movable-view"
|
||||
direction="all"
|
||||
:inertia="true"
|
||||
damping="100"
|
||||
scale="true"
|
||||
scale-min="1"
|
||||
scale-max="4"
|
||||
:scale-value="scale"
|
||||
>
|
||||
<scroll-view scroll-y="true" class="uni-scroll-view">
|
||||
<view class="scroll-view">
|
||||
<image
|
||||
:key="index"
|
||||
class="image"
|
||||
:src="item"
|
||||
mode="widthFix"
|
||||
@longpress="onLongpress(item)"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
urls: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => {
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
current: 0, //当前页
|
||||
scale: 1,
|
||||
isZooming: false, // 是否处于缩放状态
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(current) {
|
||||
this.current = this.urls.findIndex((item) => item === current)
|
||||
},
|
||||
//关闭
|
||||
close() {
|
||||
if (!this.isZooming) {
|
||||
this.show = false
|
||||
this.current = 0
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
//图片改变
|
||||
swiperChange(e) {
|
||||
this.current = e.detail.current
|
||||
},
|
||||
//监听长按
|
||||
onLongpress(e) {
|
||||
this.$emit('onLongpress', e)
|
||||
},
|
||||
handleTouchStart() {
|
||||
this.isZooming = true
|
||||
},
|
||||
handleTouchEnd() {
|
||||
this.isZooming = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.previewImage {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
.swiper {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
.swiperItem {
|
||||
.movable-area {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.movable-view {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
.uni-scroll-view {
|
||||
height: 100vh;
|
||||
}
|
||||
.scroll-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
.image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.page {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
top: 60rpx;
|
||||
text-align: center;
|
||||
.text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 3rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
JeecgUniapp-master/src/components/JMap/index.vue
Normal file
49
JeecgUniapp-master/src/components/JMap/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<view :style="[{backgroundColor:backgroundColor}]">
|
||||
<map
|
||||
v-if="latitude && longitude"
|
||||
class="map-content"
|
||||
:latitude="latitude"
|
||||
:longitude="longitude"
|
||||
:markers="marker"
|
||||
:scale="scale"
|
||||
:circles="circles"
|
||||
>
|
||||
</map>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import useGeo from '@/hooks/useGeoPosition'
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
compLatitude: {
|
||||
type: Number,
|
||||
default: 40.009390,
|
||||
required: false
|
||||
},
|
||||
compLongitude: {
|
||||
type: Number,
|
||||
default: 116.374322,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['success','fail']);
|
||||
const backgroundColor = 'transparent';
|
||||
// 引入地理定位逻辑
|
||||
let [{inCircle,getAddress,getMyAddress,latitude,longitude,marker,scale,circles},{refreshLocation}] = useGeo({ compLatitude: props.compLatitude, compLongitude: props.compLongitude }, emit);
|
||||
|
||||
defineExpose({
|
||||
inCircle,
|
||||
getAddress,
|
||||
getMyAddress,
|
||||
refreshLocation
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.map-content {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
</style>
|
||||
341
JeecgUniapp-master/src/components/LFile/LFile.vue
Normal file
341
JeecgUniapp-master/src/components/LFile/LFile.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class='t-toptips' :style="{top: top,background: cubgColor}" :class="[show?'t-top-show':'']">
|
||||
<view v-if="loading" class="flex flex-sub">
|
||||
<view class="cu-progress flex-sub round striped active sm">
|
||||
<view :style="{ background: color,width: value + '%'}"></view>
|
||||
</view>
|
||||
<text class="margin-left">{{value}}%</text>
|
||||
</view>
|
||||
<block v-else>{{msg}}</block>
|
||||
<!-- #ifdef H5 -->
|
||||
<view ref="input" class="input"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from 'vue'
|
||||
import {useToast} from "wot-design-uni";
|
||||
import {http} from "@/utils/http";
|
||||
|
||||
const props = defineProps({
|
||||
top: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'rgba(49, 126, 243, 0.5)',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#e54d42',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
}
|
||||
})
|
||||
const toast = useToast()
|
||||
const emits = defineEmits(['up-success', 'up-error'])
|
||||
|
||||
const cubgColor = ref('')
|
||||
const loading = ref(false)
|
||||
const value = ref(5)
|
||||
const show = ref(false)
|
||||
const msg = ref('执行完毕~')
|
||||
const input = ref(null)
|
||||
|
||||
const getRequest = (url) => {
|
||||
let theRequest = {}
|
||||
let index = url.indexOf("?")
|
||||
if (index != -1) {
|
||||
let str = url.substring(index+1)
|
||||
let strs = str.split("&")
|
||||
for(let i = 0; i < strs.length; i++) {
|
||||
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1])
|
||||
}
|
||||
}
|
||||
return theRequest
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param currentWebview 当前窗口webview对象
|
||||
* @param url 上传接口地址
|
||||
* @param name 上传文件key值
|
||||
* @param header 上传接口请求头
|
||||
* @param formData body内其他参数
|
||||
*/
|
||||
const appChooseFile = ({currentWebview, url, name = 'file', header, ...formData} = {} as any) => {
|
||||
// #ifdef APP-PLUS
|
||||
let wv = plus.webview.create("","/hybrid/html/index.html",{
|
||||
//'uni-app': 'none',
|
||||
top: '0px',
|
||||
height: '100%',
|
||||
background: 'transparent'
|
||||
},{
|
||||
url,
|
||||
header,
|
||||
formData,
|
||||
key: name,
|
||||
})
|
||||
wv.loadURL("/hybrid/html/index.html")
|
||||
currentWebview.append(wv)
|
||||
wv.overrideUrlLoading({mode:'reject'},(e)=>{
|
||||
let {fileName, id, fileSize} = getRequest(e.url) as any
|
||||
fileName = unescape(fileName)
|
||||
id = unescape(id)
|
||||
fileSize = unescape(fileSize)
|
||||
console.log("fileSize", fileSize)
|
||||
return onCommit(
|
||||
emits('up-success', {fileName, fileSize, data: {id, statusCode: 200}})
|
||||
)
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param url 上传接口地址
|
||||
* @param name 上传文件key值
|
||||
* @param header 上传接口请求头
|
||||
* @param formData body内其他参数
|
||||
*/
|
||||
const h5ChooseFile = ({url, name = 'file', header, ...formData} = {} as any) => {
|
||||
// #ifdef H5
|
||||
const inputEl = document.createElement('input');
|
||||
inputEl.type = 'file'
|
||||
inputEl.style.display = 'none'
|
||||
inputEl.id = 'file'
|
||||
if(props.type === 'image'){
|
||||
inputEl.accept = 'image/*'
|
||||
}
|
||||
input.value.$el.appendChild(inputEl)
|
||||
|
||||
document.getElementById("file").click()
|
||||
inputEl.onchange = (event) => {
|
||||
uploadH5(event, url, header)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @param name
|
||||
* @param header
|
||||
* @param formData
|
||||
*/
|
||||
const wxChooseFile = ({url, name = 'file', header, ...formData} = {} as any) => {
|
||||
wx.chooseMessageFile({
|
||||
count: 1,
|
||||
type: 'file',
|
||||
success: ({tempFiles}) => {
|
||||
let [{path: filePath, name: fileName}] = tempFiles
|
||||
setDefUI()
|
||||
console.log("tempFiles==>::", tempFiles)
|
||||
console.log("filePath==>::", filePath)
|
||||
console.log("url==>::", url)
|
||||
return http.upload(url, {
|
||||
filePath: filePath,
|
||||
name: 'file'
|
||||
}).then((res : any) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log("wxChooseFile返回值success::", res)
|
||||
let data = res.data
|
||||
return onCommit(emits('up-success', {fileName, data}))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("wxChooseFile返回值err", err)
|
||||
return errorHandler('文件上传失败', upErr)
|
||||
})
|
||||
},
|
||||
fail: () => errorHandler('文件选择失败', upErr)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 上传
|
||||
* @param param
|
||||
*/
|
||||
const upload = (param = {} as any) => {
|
||||
if (!param.url) {
|
||||
toast.warning('上传地址不正确')
|
||||
return
|
||||
}
|
||||
|
||||
if (loading.value) {
|
||||
toast.warning('还有个文件玩命处理中,请稍候..')
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
return appChooseFile(param)
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 打开文件
|
||||
* @param filePath
|
||||
*/
|
||||
const open = (filePath) => {
|
||||
let system = uni.getSystemInfoSync().platform
|
||||
if(system == 'ios'){
|
||||
filePath = encodeURI(filePath)
|
||||
}
|
||||
uni.openDocument({
|
||||
filePath,
|
||||
success: (res) => { console.log('打开文档成功') }
|
||||
})
|
||||
}
|
||||
/**
|
||||
* type: temporary=返回临时地址,local=长期保存到本地
|
||||
* @param url
|
||||
* @param type
|
||||
*/
|
||||
const download = (url, type = 'temporary') => {
|
||||
if (loading.value) {
|
||||
toast.warning('还有个文件玩命处理中,请稍候..')
|
||||
return
|
||||
}
|
||||
setDefUI()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let downloadTask = uni.downloadFile({
|
||||
url,
|
||||
success: ({statusCode, tempFilePath}) => {
|
||||
if (statusCode === 200) {
|
||||
if (type == 'local') {
|
||||
uni.saveFile({
|
||||
tempFilePath,
|
||||
success: ({savedFilePath}) => onCommit(resolve(savedFilePath)),
|
||||
fail: () => errorHandler('下载失败', reject)
|
||||
})
|
||||
}
|
||||
else {
|
||||
onCommit(resolve(tempFilePath))
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: () => errorHandler('下载失败', reject)
|
||||
})
|
||||
|
||||
downloadTask.onProgressUpdate(({progress = 0}) => {
|
||||
if (progress <= 100) {
|
||||
nextTick(() => {
|
||||
value.value = progress
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const onCommit = (resolve) => {
|
||||
msg.value = '执行完毕~'
|
||||
loading.value = false
|
||||
cubgColor.value = 'rgba(57, 181, 74, 0.5)'
|
||||
setTimeout(() => { show.value = false }, 1500)
|
||||
return resolve
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
const uploadH5 = (event, url, header) => {
|
||||
let _file = event.target.files[0]
|
||||
let formData = new FormData()
|
||||
formData.append("file", _file)
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url, true)
|
||||
|
||||
for (let keys in header) {
|
||||
xhr.setRequestHeader(keys, header[keys])
|
||||
}
|
||||
|
||||
xhr.upload.onloadstart = () => {
|
||||
toast.loading('上传中...')
|
||||
}
|
||||
|
||||
xhr.upload.onprogress = (res) => {
|
||||
if(res.loaded / res.total == 1){
|
||||
setTimeout(() => {
|
||||
toast.close()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onload = (res:any) => {
|
||||
console.log("上传完成", res)
|
||||
console.log(res.target.response)
|
||||
|
||||
toast.close()
|
||||
if (res.target.status === 200) {
|
||||
let data = JSON.parse(res.target.response)
|
||||
console.log("data", data)
|
||||
let fileName = data.message
|
||||
onCommit(emits('up-success', {fileSize: _file.size, fileName, data}))
|
||||
} else {
|
||||
toast.error('文件上传失败')
|
||||
}
|
||||
const inputEl = document.getElementById("file");
|
||||
input.value.$el.removeChild(inputEl)
|
||||
}
|
||||
|
||||
xhr.onerror = (data) => {
|
||||
toast.close()
|
||||
toast.error('上传失败')
|
||||
}
|
||||
|
||||
xhr.ontimeout = function(event) {
|
||||
toast.close()
|
||||
toast.error('上传超时,请刷新重试')
|
||||
}
|
||||
|
||||
xhr.send(formData)
|
||||
}
|
||||
// #endif
|
||||
|
||||
const setDefUI = () => {
|
||||
cubgColor.value = props.bgColor
|
||||
value.value = 0
|
||||
loading.value = true
|
||||
show.value = true
|
||||
}
|
||||
|
||||
const upErr = (errText) => {
|
||||
emits('up-error', errText)
|
||||
}
|
||||
|
||||
const errorHandler = (errText, reject) => {
|
||||
msg.value = errText
|
||||
loading.value = false
|
||||
cubgColor.value = 'rgba(229, 77, 66, 0.5)'
|
||||
setTimeout(() => { show.value = false }, 1500)
|
||||
return reject(errText)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
upload,
|
||||
open,
|
||||
download
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.t-toptips {
|
||||
width: 100%;
|
||||
padding: 18upx 30upx;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
z-index: 90;
|
||||
color: #fff;
|
||||
font-size: 30upx;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
word-break: break-all;
|
||||
display: none;
|
||||
transform: translateZ(0) translateY(-100%);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.t-top-show {
|
||||
transform: translateZ(0) translateY(0);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
296
JeecgUniapp-master/src/components/PageLayout/PageLayout.vue
Normal file
296
JeecgUniapp-master/src/components/PageLayout/PageLayout.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<view class="pageLayout">
|
||||
<view
|
||||
v-if="navbarShow"
|
||||
:class="{ pageNav: true, transparent: navBgTransparent, fixed: navFixed }"
|
||||
:style="{ height: `${statusBarHeight + navHeight}px` }"
|
||||
>
|
||||
<view class="statusBar" :style="{ height: `${statusBarHeight}px` }"></view>
|
||||
<wd-navbar
|
||||
:bordered="false"
|
||||
:title="navTitle"
|
||||
:leftText="navLeftText"
|
||||
:leftArrow="navLeftArrow"
|
||||
:rightText="isMp ? '' : navRightText"
|
||||
@clickLeft="handleClickLeft"
|
||||
@clickRight="handleClickRight"
|
||||
:custom-class="getClass()"
|
||||
>
|
||||
<template v-if="isMp === false && $slots.navRight" #right>
|
||||
<slot name="navRight"></slot>
|
||||
</template>
|
||||
<template v-if="isMp" #left>
|
||||
<!-- 为了在小程序上美观 -->
|
||||
<template v-if="$slots.navRight && isShowNavRightTextMp">
|
||||
<view class="btnGroup">
|
||||
<view class="left" @click.stop="handleClickLeft">{{ navLeftText }}</view>
|
||||
<view class="right" @click.stop="() => emit('navRightMp')">{{ navRightTextMp }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else-if="$slots.navLeft">
|
||||
<slot name="navLeft"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="mpNavLeft" @click.stop="handleClickLeft">
|
||||
<wd-icon v-if="navLeftArrow" name="thin-arrow-left" size="14px"></wd-icon>
|
||||
<view>{{ navLeftText }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
</view>
|
||||
<view class="pageContent">
|
||||
<view class="content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<!--微信中导航右侧的在这儿生成-->
|
||||
<view class="navRight" v-if="isMp && !$slots.navRight && mpNavRightIsBottom && navRightText">
|
||||
<wd-button @click="handleClickRight">{{ navRightText }}</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tabbar"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
import { useRouter } from '@/plugin/uni-mini-router'
|
||||
import { useParamsStore } from '@/store/page-params'
|
||||
import { isMp } from '@/utils/platform'
|
||||
// const isMp = true
|
||||
defineOptions({
|
||||
name: 'pageLayout',
|
||||
options: {
|
||||
// apply-shared:当前页面样式会影响到子组件样式.(小程序)
|
||||
// shared:当前页面样式影响到子组件,子组件样式也会影响到当前页面.(小程序)
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const paramsStore = useParamsStore()
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
backRouteName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
backRoutePath: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
routeParams: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
routeQuery: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
routeMethod: {
|
||||
type: String,
|
||||
default: 'replace',
|
||||
},
|
||||
navbarShow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
navBgTransparent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
navFixed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'page', // 'page','popup'
|
||||
},
|
||||
navTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
navLeftText: {
|
||||
type: String,
|
||||
default: '返回',
|
||||
},
|
||||
navLeftArrow: {
|
||||
typeof: Boolean,
|
||||
default: true,
|
||||
},
|
||||
navRightText: {
|
||||
typeof: String,
|
||||
default: '',
|
||||
},
|
||||
// 当有右侧有slot时,在小程序上不显示slot,显示文字 (默认显示)
|
||||
isShowNavRightTextMp: {
|
||||
typeof: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 当有右侧有slot时,在小程序上不显示slot,显示文字
|
||||
navRightTextMp: {
|
||||
typeof: String,
|
||||
default: '确定',
|
||||
},
|
||||
// 右侧的文本在小程序端是否生成在页面底部(否:就得业务中自己写)
|
||||
mpNavRightIsBottom: {
|
||||
typeof: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const slot = useSlots()
|
||||
const globalData = getApp().globalData
|
||||
const { systemInfo, navHeight } = globalData
|
||||
const { statusBarHeight } = systemInfo
|
||||
const emit = defineEmits(['navBack', 'navRight', 'navRightMp'])
|
||||
const handleClickLeft = () => {
|
||||
emit('navBack')
|
||||
// 只有在页面中才默认返回,弹层中不返回
|
||||
if (props.type === 'page') {
|
||||
const pages = getCurrentPages()
|
||||
if (props.backRouteName || props.backRoutePath) {
|
||||
const prevPage = pages[pages.length - 2]
|
||||
if (prevPage) {
|
||||
const route = prevPage.route
|
||||
const name = route.split('/').pop()
|
||||
if (route === props.backRoutePath || props.backRouteName === name) {
|
||||
router.back()
|
||||
clearPageParamsCache()
|
||||
return
|
||||
}
|
||||
}
|
||||
if (props.backRouteName) {
|
||||
router[props.routeMethod]({ name: props.backRouteName, params: props.routeParams })
|
||||
clearPageParamsCache()
|
||||
} else {
|
||||
router[props.routeMethod]({ name: props.backRoutePath, query: props.routeQuery })
|
||||
clearPageParamsCache()
|
||||
}
|
||||
} else {
|
||||
router.back()
|
||||
clearPageParamsCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
const clearPageParamsCache = () => {
|
||||
// 清除页面传参缓存
|
||||
const pages = getCurrentPages()
|
||||
const curPage = pages[pages.length - 1]
|
||||
const curRoute = curPage.route
|
||||
const name = curRoute.split('/').pop()
|
||||
paramsStore.clearPageParams(name)
|
||||
}
|
||||
const handleClickRight = () => {
|
||||
emit('navRight')
|
||||
}
|
||||
const getClass = () => {
|
||||
const cls = `nav ${isMp ? 'mp' : ''} ${slot.navRight ? 'slot_navRight' : ''}`
|
||||
return cls
|
||||
}
|
||||
console.log('props:', props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pageLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
.pageNav {
|
||||
background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
|
||||
&.transparent {
|
||||
background-image: none;
|
||||
}
|
||||
&.fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
.statusBar {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
}
|
||||
:deep(.wd-navbar) {
|
||||
background-color: transparent;
|
||||
--wot-navbar-title-font-weight: 400;
|
||||
--wot-navbar-arrow-size: 18px;
|
||||
--wot-navbar-desc-font-size: 14px;
|
||||
--wot-navbar-title-font-size: 16px;
|
||||
&.mp {
|
||||
&.slot_navRight {
|
||||
padding-right: 90px;
|
||||
.wd-navbar__text {
|
||||
display: none;
|
||||
}
|
||||
.wd-navbar__content {
|
||||
display: flex;
|
||||
}
|
||||
.wd-navbar__left,
|
||||
.wd-navbar__title,
|
||||
.wd-navbar__right {
|
||||
position: static;
|
||||
}
|
||||
.wd-navbar__title {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pageContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #f1f1f1;
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.navRight {
|
||||
padding: 15px;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
margin-bottom: calc(constant(safe-area-inset-bottom));
|
||||
margin-bottom: calc(env(safe-area-inset-bottom));
|
||||
justify-content: flex-end;
|
||||
:deep(.wd-button) {
|
||||
--wot-button-medium-height: 38px;
|
||||
width: 70%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabbar {
|
||||
/* #ifdef H5 */
|
||||
height: var(--window-bottom);
|
||||
/* #endif */
|
||||
}
|
||||
.btnGroup {
|
||||
display: flex;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
.left {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.mpNavLeft {
|
||||
display: flex;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view :style="{ height: addUnit(height) }">
|
||||
<view :class="`wd-navbar ${customClass} ${fixed ? 'is-fixed' : ''} ${bordered ? 'is-border' : ''}`" :style="rootStyle">
|
||||
<view class="wd-navbar__content">
|
||||
<view class="wd-navbar__capsule" v-if="$slots.capsule">
|
||||
<slot name="capsule" />
|
||||
</view>
|
||||
|
||||
<view :class="`wd-navbar__left ${leftDisabled ? 'is-disabled' : ''}`" @click="handleClickLeft" v-else-if="!$slots.left">
|
||||
<wd-icon v-if="leftArrow" name="arrow-left" custom-class="wd-navbar__arrow" />
|
||||
<view v-if="leftText" class="wd-navbar__text">{{ leftText }}</view>
|
||||
</view>
|
||||
|
||||
<view v-else :class="`wd-navbar__left ${leftDisabled ? 'is-disabled' : ''}`" @click="handleClickLeft">
|
||||
<slot name="left" />
|
||||
</view>
|
||||
|
||||
<view class="wd-navbar__title">
|
||||
<slot name="title" />
|
||||
<block v-if="!$slots.title && title">{{ title }}</block>
|
||||
</view>
|
||||
<view :class="`wd-navbar__right ${rightDisabled ? 'is-disabled' : ''}`" @click="handleClickRight" v-if="$slots.right || rightText">
|
||||
<slot name="right" />
|
||||
|
||||
<view v-if="!$slots.right && rightText" class="wd-navbar__text" hover-class="wd-navbar__text--hover" :hover-stay-time="70">
|
||||
{{ rightText }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-navbar',
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type CSSProperties, computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { getRect, addUnit, isDef, objToStyle } from 'wot-design-uni/components/common/util'
|
||||
import { navbarProps } from './types'
|
||||
|
||||
const props = defineProps(navbarProps)
|
||||
const emit = defineEmits(['click-left', 'click-right'])
|
||||
|
||||
const height = ref<number | ''>('') // 占位高度
|
||||
|
||||
const { statusBarHeight } = uni.getSystemInfoSync()
|
||||
|
||||
watch(
|
||||
[() => props.fixed, () => props.placeholder],
|
||||
() => {
|
||||
setPlaceholderHeight()
|
||||
},
|
||||
{ deep: true, immediate: false }
|
||||
)
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (props.fixed && isDef(props.zIndex)) {
|
||||
style['z-index'] = props.zIndex
|
||||
}
|
||||
if (props.safeAreaInsetTop) {
|
||||
style['padding-top'] = addUnit(statusBarHeight || 0)
|
||||
}
|
||||
return `${objToStyle(style)};${props.customStyle}`
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.fixed && props.placeholder) {
|
||||
nextTick(() => {
|
||||
setPlaceholderHeight()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function handleClickLeft() {
|
||||
if (!props.leftDisabled) {
|
||||
emit('click-left')
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickRight() {
|
||||
if (!props.rightDisabled) {
|
||||
emit('click-right')
|
||||
}
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
function setPlaceholderHeight() {
|
||||
if (!props.fixed || !props.placeholder) {
|
||||
return
|
||||
}
|
||||
|
||||
getRect('.wd-navbar', false, proxy).then((res) => {
|
||||
height.value = res.height as number
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
@import "wot-design-uni/components/common/abstracts/_mixin.scss";
|
||||
@import "wot-design-uni/components/common/abstracts/variable.scss";
|
||||
|
||||
.wot-theme-dark {
|
||||
@include b(navbar) {
|
||||
background-color: $-dark-background;
|
||||
|
||||
@include e(title) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
|
||||
@include e(text) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
|
||||
:deep(.wd-navbar__arrow) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@include b(navbar) {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
height: $-navbar-height;
|
||||
line-height: $-navbar-height;
|
||||
background-color: $-navbar-background;
|
||||
box-sizing: content-box;
|
||||
|
||||
@include e(content) {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include e(title) {
|
||||
max-width: 60%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
color: $-navbar-color;
|
||||
font-weight: $-navbar-title-font-weight;
|
||||
font-size: $-navbar-title-font-size;
|
||||
@include lineEllipsis();
|
||||
}
|
||||
|
||||
@include e(text) {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: $-navbar-desc-font-color;
|
||||
}
|
||||
|
||||
@include e(left, right, capsule) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-size: $-navbar-desc-font-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
|
||||
@include when(disabled) {
|
||||
opacity: $-navbar-disabled-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(left, capsule) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@include e(right) {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@include edeep(arrow) {
|
||||
font-size: $-navbar-arrow-size;
|
||||
color: $-navbar-color;
|
||||
}
|
||||
|
||||
|
||||
@include when(border) {
|
||||
@include halfPixelBorder('bottom');
|
||||
}
|
||||
|
||||
@include when(fixed) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumberProp } from 'wot-design-uni/components/common/props'
|
||||
|
||||
export const navbarProps = {
|
||||
...baseProps,
|
||||
/**
|
||||
* 标题文字
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* 左侧文案
|
||||
*/
|
||||
leftText: String,
|
||||
/**
|
||||
* 右侧文案
|
||||
*/
|
||||
rightText: String,
|
||||
/**
|
||||
* 是否显示左侧箭头
|
||||
*/
|
||||
leftArrow: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否显示下边框
|
||||
*/
|
||||
bordered: makeBooleanProp(true),
|
||||
/**
|
||||
* 是否固定到顶部
|
||||
*/
|
||||
fixed: makeBooleanProp(false),
|
||||
/**
|
||||
* 固定在顶部时,是否在标签位置生成一个等高的占位元素
|
||||
*/
|
||||
placeholder: makeBooleanProp(false),
|
||||
/**
|
||||
* 导航栏 z-index
|
||||
*/
|
||||
zIndex: makeNumberProp(500),
|
||||
/**
|
||||
* 是否开启顶部安全区适配
|
||||
*/
|
||||
safeAreaInsetTop: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否禁用左侧按钮,禁用时透明度降低,且无法点击
|
||||
*/
|
||||
leftDisabled: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否禁用右侧按钮,禁用时透明度降低,且无法点击
|
||||
*/
|
||||
rightDisabled: makeBooleanProp(false)
|
||||
}
|
||||
|
||||
export type NavbarProps = ExtractPropTypes<typeof navbarProps>
|
||||
142
JeecgUniapp-master/src/components/Popup/Popup.vue
Normal file
142
JeecgUniapp-master/src/components/Popup/Popup.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<view class="Popup">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
type="text"
|
||||
readonly
|
||||
v-model="showText"
|
||||
clearable
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<popupReportModal
|
||||
v-if="reportModal.show"
|
||||
:code="code"
|
||||
:showFiled="reportModal.showFiled"
|
||||
:multi="multi"
|
||||
@close="handleClose"
|
||||
@change="handleChange"
|
||||
></popupReportModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import popupReportModal from './components/popupReportModal.vue'
|
||||
defineOptions({
|
||||
name: 'Popup',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
fieldConfig: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
setFieldsValue: {
|
||||
type: Function,
|
||||
required: true,
|
||||
default: () => {},
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
default: ',',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
const toast = useToast()
|
||||
const showText = ref('')
|
||||
const attrs: any = useAttrs()
|
||||
const reportModal = reactive({
|
||||
show: false,
|
||||
showFiled: props.fieldConfig.map((item) => item['source']).join(','),
|
||||
})
|
||||
if (!props.code || props.fieldConfig.length == 0) {
|
||||
toast.error('popup参数未正确配置!')
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听value数值
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
showText.value = val && val.length > 0 ? val.split(props.spliter).join(',') : ''
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
function callBack(rows) {
|
||||
let fieldConfig: any = props.fieldConfig
|
||||
//匹配popup设置的回调值
|
||||
let values = {}
|
||||
let labels = []
|
||||
for (let item of fieldConfig) {
|
||||
let val = rows.map((row) => row[item.source])
|
||||
val = val.length == 1 ? val[0] : val.join(',')
|
||||
item.target.split(',').forEach((target) => {
|
||||
values[target] = val
|
||||
})
|
||||
}
|
||||
showText.value = labels.join(',')
|
||||
props.setFieldsValue(values)
|
||||
emit('change', values)
|
||||
// emit('update:modelValue', values)
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
reportModal.show = true
|
||||
}
|
||||
}
|
||||
const handleClose = () => {
|
||||
reportModal.show = false
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
console.log('选中的值:', data)
|
||||
callBack(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout
|
||||
:navTitle="navTitle"
|
||||
type="popup"
|
||||
navRightText="确定"
|
||||
@navRight="handleConfirm"
|
||||
@navBack="handleCancel"
|
||||
>
|
||||
<view class="wrap">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
:fixed="false"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:default-page-size="15"
|
||||
>
|
||||
<template #top>
|
||||
<wd-search
|
||||
hide-cancel
|
||||
:placeholder="search.placeholder"
|
||||
v-model="search.keyword"
|
||||
@search="handleSearch"
|
||||
@clear="handleClear"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="multi">
|
||||
<wd-checkbox-group shape="square" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<view class="list" @click="hanldeCheck(index)">
|
||||
<view class="left text-gray-5">
|
||||
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||
<view class="row">
|
||||
<text class="label">{{ cItem.title }}:</text>
|
||||
<text class="value">{{ item[cItem.dataIndex] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-checkbox ref="checkboxRef" :modelValue="index"></wd-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-checkbox-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<wd-radio-group shape="dot" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<wd-cell>
|
||||
<view class="list" @click="hanldeCheck(index)">
|
||||
<view class="left text-gray-5">
|
||||
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||
<view class="row">
|
||||
<text class="label">{{ cItem.title }}:</text>
|
||||
<text class="value">{{ item[cItem.dataIndex] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-radio :value="index"></wd-radio>
|
||||
</view>
|
||||
</view>
|
||||
</wd-cell>
|
||||
</template>
|
||||
</wd-radio-group>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import { isArray } from '@/utils/is'
|
||||
defineOptions({
|
||||
name: 'popupReportModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
showFiled: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const toast = useToast()
|
||||
const show = ref(true)
|
||||
const api = {
|
||||
getColumns: '/online/cgreport/api/getRpColumns',
|
||||
getData: '/online/cgreport/api/getData',
|
||||
getQueryInfo: '/online/cgreport/api/getQueryInfo',
|
||||
}
|
||||
console.log('props:::', props)
|
||||
const navTitle = ref('')
|
||||
const paging = ref(null)
|
||||
const dataList = ref([])
|
||||
// 报表id
|
||||
let rpConfigId = null
|
||||
let loadedColumns = false
|
||||
const dictOptions = ref([])
|
||||
const columns = ref([])
|
||||
const checkedValue: any = ref(props.multi ? [] : '')
|
||||
const checkboxRef = ref(null)
|
||||
const search = reactive({
|
||||
keyword: '',
|
||||
placeholder: '',
|
||||
field: '',
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if (checkedValue.value.length == 0) {
|
||||
toast.warning('还没选择~')
|
||||
return
|
||||
}
|
||||
const result = []
|
||||
let value = checkedValue.value
|
||||
if (!Array.isArray(checkedValue.value)) {
|
||||
value = [checkedValue.value]
|
||||
}
|
||||
value.forEach((index) => {
|
||||
result.push(dataList.value[index])
|
||||
})
|
||||
show.value = false
|
||||
emit('change', result)
|
||||
handleClose()
|
||||
}
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
paging.value.reload()
|
||||
}
|
||||
// 清除搜索条件
|
||||
function handleClear() {
|
||||
search.keyword = ''
|
||||
handleSearch()
|
||||
}
|
||||
const hanldeCheck = (index) => {
|
||||
if (props.multi) {
|
||||
if (Array.isArray(checkboxRef.value)) {
|
||||
checkboxRef.value[index].toggle()
|
||||
}
|
||||
} else {
|
||||
checkedValue.value = index
|
||||
}
|
||||
}
|
||||
const getQueryInfo = () => {
|
||||
const analysis = (data = []) => {
|
||||
if (data.length) {
|
||||
search.placeholder = `请输入${data[0].label}`
|
||||
search.field = data[0].field
|
||||
} else {
|
||||
const item = columns[0] ?? {}
|
||||
search.placeholder = `请输入${item.title}`
|
||||
search.field = item.dataIndex
|
||||
}
|
||||
}
|
||||
http
|
||||
.get(`${api.getQueryInfo}/${rpConfigId}`)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
analysis(res.result)
|
||||
} else {
|
||||
analysis()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
analysis()
|
||||
})
|
||||
}
|
||||
const getRpColumns = () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (loadedColumns) {
|
||||
resolve()
|
||||
} else {
|
||||
http
|
||||
.get(`${api.getColumns}/${props.code}`)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
loadedColumns = true
|
||||
const { result } = res
|
||||
navTitle.value = result.cgRpConfigName
|
||||
dictOptions.value = result.dictOptions
|
||||
rpConfigId = result.cgRpConfigId
|
||||
const fileds = props.showFiled.split(',')[0]
|
||||
result.columns?.forEach((item) => {
|
||||
if (fileds.includes(item.dataIndex)) {
|
||||
columns.value.push(item)
|
||||
}
|
||||
})
|
||||
// 最少展示三个字段(字段够多时)
|
||||
const minNum = 3
|
||||
if (columns.value.length < minNum) {
|
||||
result.columns?.forEach((item) => {
|
||||
const findItem = columns.value.find((o) => o.dataIndex == item.dataIndex);
|
||||
if (!findItem) {
|
||||
if (columns.value.length < minNum) {
|
||||
columns.value.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
getQueryInfo()
|
||||
resolve()
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
const pararms = { pageNo, pageSize }
|
||||
if (search.keyword) {
|
||||
pararms[search.field] = `*${search.keyword}*`
|
||||
}
|
||||
getRpColumns()
|
||||
.then(() => {
|
||||
http
|
||||
.get(`${api.getData}/${rpConfigId}`, pararms)
|
||||
.then((res: any) => {
|
||||
if (res.success && res.result.records) {
|
||||
paging.value.complete(res.result.records ?? [])
|
||||
} else {
|
||||
paging.value.complete(false)
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-cell) {
|
||||
--wot-color-white: tranparent;
|
||||
--wot-cell-padding: 0;
|
||||
.wd-cell__wrapper {
|
||||
--wot-cell-wrapper-padding: 0;
|
||||
}
|
||||
.wd-cell__left {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.wd-checkbox-group) {
|
||||
--wot-checkbox-bg: tranparent;
|
||||
}
|
||||
:deep(.wd-radio-group) {
|
||||
--wot-radio-bg: tranparent;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
line-height: 1.8;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
:deep(.wd-checkbox) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrap {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
219
JeecgUniapp-master/src/components/PopupDict/PopupDict.vue
Normal file
219
JeecgUniapp-master/src/components/PopupDict/PopupDict.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view class="PopupDict">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-select-picker
|
||||
v-model="showText"
|
||||
:columns="options"
|
||||
readonly
|
||||
:type="multi ? 'checkbox' : 'radio'"
|
||||
@click="handleClick"
|
||||
v-bind="$attrs"
|
||||
></wd-select-picker>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<popupReportModal
|
||||
v-if="reportModal.show"
|
||||
:code="code"
|
||||
:showFiled="labelFiled"
|
||||
:multi="multi"
|
||||
@close="handleClose"
|
||||
@change="handleChange"
|
||||
></popupReportModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import popupReportModal from '@/components/Popup/components/popupReportModal.vue'
|
||||
defineOptions({
|
||||
name: 'PopupDict',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
dictCode: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
default: ',',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
const toast = useToast()
|
||||
const showText = ref<any>(props.multi ? [] : '')
|
||||
const options = ref<any>([])
|
||||
const cgRpConfigId = ref('')
|
||||
const code = ref(props.dictCode.split(',')[0])
|
||||
const labelFiled = ref(props.dictCode.split(',')[1])
|
||||
const valueFiled = ref(props.dictCode.split(',')[2])
|
||||
const reportModal = reactive({
|
||||
show: false,
|
||||
})
|
||||
//定义请求url信息
|
||||
const configUrl = reactive({
|
||||
getColumns: '/online/cgreport/api/getRpColumns/',
|
||||
getData: '/online/cgreport/api/getData/',
|
||||
})
|
||||
|
||||
if (!code.value || !valueFiled.value || !labelFiled.value) {
|
||||
toast.error('popupDict参数未正确配置!')
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听value数值
|
||||
*/
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
const callBack = () => {
|
||||
if (props.multi) {
|
||||
showText.value = val && val.length > 0 ? val.split(props.spliter) : []
|
||||
} else {
|
||||
showText.value = val ?? ''
|
||||
}
|
||||
}
|
||||
if (props.modelValue) {
|
||||
if (cgRpConfigId.value) {
|
||||
loadData({ callBack })
|
||||
} else {
|
||||
loadColumnsInfo({ callBack })
|
||||
}
|
||||
} else {
|
||||
callBack()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
watch(
|
||||
() => showText.value,
|
||||
(val) => {
|
||||
let result
|
||||
if (props.multi) {
|
||||
result = val.join(',')
|
||||
} else {
|
||||
result = val
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('change', result)
|
||||
emit('update:modelValue', result)
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* 加载列信息
|
||||
*/
|
||||
function loadColumnsInfo({ callBack }) {
|
||||
let url = `${configUrl.getColumns}${code.value}`
|
||||
http
|
||||
.get(url)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
cgRpConfigId.value = res.result.cgRpConfigId
|
||||
loadData({ callBack })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
callBack?.()
|
||||
})
|
||||
}
|
||||
function loadData({ callBack }) {
|
||||
let url = `${configUrl.getData}${unref(cgRpConfigId)}`
|
||||
http
|
||||
.get(url, { ['force_' + valueFiled.value]: props.modelValue })
|
||||
.then((res: any) => {
|
||||
let data = res.result
|
||||
if (data.records?.length) {
|
||||
options.value = data.records.map((item) => {
|
||||
return { value: item[valueFiled.value], label: item[labelFiled.value] }
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
callBack?.()
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 传值回调
|
||||
*/
|
||||
function callBack(rows) {
|
||||
const dataOptions: any = []
|
||||
const dataValue: any = []
|
||||
let result
|
||||
rows.forEach((item) => {
|
||||
dataOptions.push({ value: item[valueFiled.value], label: item[labelFiled.value] })
|
||||
dataValue.push(item[valueFiled.value])
|
||||
})
|
||||
options.value = dataOptions
|
||||
if (props.multi) {
|
||||
showText.value = dataValue
|
||||
result = dataValue.join(props.spliter)
|
||||
} else {
|
||||
showText.value = dataValue[0]
|
||||
result = dataValue[0]
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('change', result)
|
||||
emit('update:modelValue', result)
|
||||
})
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
if (!props.disabled) {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
reportModal.show = true
|
||||
}
|
||||
}
|
||||
const handleClose = () => {
|
||||
reportModal.show = false
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
console.log('选中的值:', data)
|
||||
callBack(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
JeecgUniapp-master/src/components/ProgressMap/ProgressMap.vue
Normal file
136
JeecgUniapp-master/src/components/ProgressMap/ProgressMap.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="ProgressMap">
|
||||
<view v-if="title.length" class="title">{{ title }}</view>
|
||||
<view
|
||||
:class="{
|
||||
stepBox: true,
|
||||
active: item.activeStep,
|
||||
'u-iconfont': true,
|
||||
'u-icon-clock': !item.activeStep,
|
||||
'u-icon-success': item.activeStep,
|
||||
}"
|
||||
v-for="(item, index) in dataSource"
|
||||
:key="index"
|
||||
>
|
||||
<view :class="{ stepContent: true, active: item.activeStep }">
|
||||
<template v-for="(inItem, inIndex) in item.data" :key="inIndex">
|
||||
<view v-if="inItem.label" class="item">
|
||||
{{ inItem.label }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view v-if="item.file" class="file">
|
||||
<template v-for="(file, inIndex) in item.file" :key="inIndex">
|
||||
<view :data-url="file.filePath" class="item" @click="handClick(file)">
|
||||
<view class="u-iconfont u-icon-link"></view>
|
||||
<view class="text ellipsis">{{ file.fileName }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<ImgPreview
|
||||
v-if="imgPreview.show"
|
||||
:urls="imgPreview.urls"
|
||||
@close="() => (imgPreview.show = false)"
|
||||
></ImgPreview>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { downloadFile } from '@/common/uitls'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
|
||||
const props: any = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [{ activeStep: false, data: [] }],
|
||||
},
|
||||
})
|
||||
const imgPreview = reactive({
|
||||
show: false,
|
||||
urls: [],
|
||||
})
|
||||
const handClick = (file) => {
|
||||
const suffix = file.filePath.split('.').pop()
|
||||
if (['gif', 'png', 'jpg', 'jpeg'].includes(suffix)) {
|
||||
imgPreview.urls = [getFileAccessHttpUrl(file.filePath)]
|
||||
imgPreview.show = true
|
||||
} else {
|
||||
downloadFile(file.filePath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ProgressMap {
|
||||
background-color: #fff;
|
||||
}
|
||||
.title {
|
||||
padding: 20upx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stepBox {
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
padding: 32upx 32upx 32upx 120upx;
|
||||
color: #aaaaaa;
|
||||
&.active {
|
||||
color: #39b54a;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 38upx;
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
top: 50upx;
|
||||
border-radius: 50%;
|
||||
margin: 8upx;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0.5px;
|
||||
background-color: #ddd;
|
||||
left: 60upx;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
z-index: 8;
|
||||
}
|
||||
.stepContent {
|
||||
padding: 10upx 50upx 10upx 30upx;
|
||||
border-radius: 8upx;
|
||||
background-color: #f0f0f0;
|
||||
color: #333333;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
&.active {
|
||||
font-weight: 500;
|
||||
background-color: #39b54a;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 5px #39b54a;
|
||||
}
|
||||
}
|
||||
.file {
|
||||
padding-top: 10px;
|
||||
.item {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
color: var(--color-blue);
|
||||
line-height: 1.2;
|
||||
.u-iconfont {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<wd-popup v-model="show" position="right" @close="handleClose">
|
||||
<view class="content">
|
||||
<wd-text v-if="title" :text="title"></wd-text>
|
||||
<wd-cell-group border>
|
||||
<wd-radio-group v-model="checked">
|
||||
<template v-for="(item, index) in options">
|
||||
<wd-cell :title="item.title" clickable @click="handleSelected(item)">
|
||||
<wd-radio :value="item.key"></wd-radio>
|
||||
</wd-cell>
|
||||
</template>
|
||||
</wd-radio-group>
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { hasRoute, cache } from '@/common/uitls'
|
||||
import { ref } from 'vue'
|
||||
const eimt = defineEmits(['change', 'close'])
|
||||
const show = ref(true)
|
||||
const props = defineProps(['title', 'data', 'options', 'checked'])
|
||||
const checked = ref(props.checked)
|
||||
const handleClose = () => {
|
||||
show.value = false
|
||||
setTimeout(() => {
|
||||
eimt('close')
|
||||
}, 300)
|
||||
}
|
||||
const handleSelected = (item) => {
|
||||
checked.value = item.key
|
||||
eimt('change', { option: item, data: props.data })
|
||||
handleClose()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
max-width: 200px;
|
||||
padding: 10px;
|
||||
.wd-text.is-default {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.wd-cell {
|
||||
padding-left: 0;
|
||||
--wot-cell-label-color: #444;
|
||||
--wot-cell-label-fs: 14px;
|
||||
&.red {
|
||||
color: red;
|
||||
--wot-cell-label-color: red;
|
||||
}
|
||||
}
|
||||
.wd-cell-group {
|
||||
:deep(.wd-cell__wrapper) {
|
||||
align-items: center;
|
||||
.wd-cell__right {
|
||||
flex: none;
|
||||
width: 24px;
|
||||
}
|
||||
.wd-radio {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
285
JeecgUniapp-master/src/components/SelectDept/SelectDept.vue
Normal file
285
JeecgUniapp-master/src/components/SelectDept/SelectDept.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<view class="CategorySelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
v-model="showText"
|
||||
clearable
|
||||
readonly
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" position="bottom" v-model="popupShow">
|
||||
<view class="content">
|
||||
<view class="operation">
|
||||
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
|
||||
<view class="confrim" @click.stop="confirm">确定</view>
|
||||
</view>
|
||||
<scroll-view class="flex-1" scroll-y>
|
||||
<DaTree
|
||||
:data="treeData"
|
||||
:labelField="labelKey"
|
||||
:valueField="rowKey"
|
||||
loadMode
|
||||
:showCheckbox="multiple"
|
||||
:showRadioIcon="false"
|
||||
:checkStrictly="true"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
:loadApi="asyncLoadTreeData"
|
||||
@change="handleTreeChange"
|
||||
></DaTree>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import DaTree from '@/uni_modules/da-tree/index.vue'
|
||||
import { isArray } from '@/utils/is'
|
||||
import { getPlaceholder } from '@/common/uitls'
|
||||
defineOptions({
|
||||
name: 'SelectDept',
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'key',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'title',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const toast = useToast()
|
||||
const api = {
|
||||
loadDictItem: '/sys/category/loadDictItem/',
|
||||
queryDepartTreeSync: '/sys/sysDepart/queryDepartTreeSync',
|
||||
}
|
||||
const showText = ref('')
|
||||
const popupShow = ref(false)
|
||||
const treeData = ref<any[]>([])
|
||||
const treeValue = ref([])
|
||||
const defaultCheckedKeys: any = ref(props.multiple ? [] : '')
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
popupShow.value = true
|
||||
}
|
||||
}
|
||||
const cancel = () => {
|
||||
popupShow.value = false
|
||||
}
|
||||
const confirm = () => {
|
||||
const titles = treeValue.value.map((item) => item.title)
|
||||
const keys = treeValue.value.map((item) => item.key).join(',')
|
||||
showText.value = titles.join(',')
|
||||
popupShow.value = false
|
||||
emit('update:modelValue', keys)
|
||||
emit('change', keys)
|
||||
}
|
||||
|
||||
const handleTreeChange = (value, record) => {
|
||||
const { originItem, checkedStatus } = record
|
||||
if (checkedStatus) {
|
||||
// 选中
|
||||
if (props.multiple) {
|
||||
treeValue.value.push({ key: originItem[props.rowKey], title: originItem[props.labelKey] })
|
||||
} else {
|
||||
treeValue.value = [{ key: originItem[props.rowKey], title: originItem[props.labelKey] }]
|
||||
}
|
||||
} else {
|
||||
// 取消
|
||||
if (props.multiple) {
|
||||
const findIndex = treeValue.value.findIndex(
|
||||
(item) => item[props.rowKey] == originItem[props.rowKey],
|
||||
)
|
||||
if (findIndex != -1) {
|
||||
treeValue.value.splice(findIndex, 1)
|
||||
}
|
||||
} else {
|
||||
treeValue.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
const transformField = (result) => {
|
||||
for (let i of result) {
|
||||
i.value = i.key
|
||||
if (i.isLeaf == false) {
|
||||
i.leaf = false
|
||||
} else if (i.isLeaf == true) {
|
||||
i.leaf = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// 异步加载
|
||||
const asyncLoadTreeData = ({ originItem }) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
let param = {
|
||||
pid: originItem.key,
|
||||
primaryKey: props.rowKey,
|
||||
}
|
||||
http
|
||||
.get(api.queryDepartTreeSync, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
transformField(result)
|
||||
resolve(result)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
.catch((err) => resolve(null))
|
||||
})
|
||||
}
|
||||
// 加载根节点
|
||||
function loadRoot() {
|
||||
let param = {
|
||||
primaryKey: props.rowKey,
|
||||
}
|
||||
http
|
||||
.get(api.queryDepartTreeSync, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
if (result && result.length > 0) {
|
||||
transformField(result)
|
||||
treeData.value = result
|
||||
}
|
||||
} else {
|
||||
console.error('部门组件加载根节点数据失败~')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('部门组件加载根节点数据失败~')
|
||||
})
|
||||
}
|
||||
// 翻译input内的值
|
||||
function loadItemByCode() {
|
||||
let value = props.modelValue
|
||||
if(value){
|
||||
console.log('部门组件翻译props.modelValue', props.modelValue)
|
||||
if (isArray(props.modelValue)) {
|
||||
// @ts-ignore
|
||||
value = value.join(',')
|
||||
}
|
||||
if (value === treeData.value.map((item) => item.key).join(',')) {
|
||||
// 说明是刚选完,内部已有翻译。不需要再请求
|
||||
return
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(api.queryDepartTreeSync, { ids: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.map((item) => item[props.labelKey]).join(',')
|
||||
if (props.multiple) {
|
||||
defaultCheckedKeys.value = typeof value === 'string' ? value.split(',') : [value]
|
||||
} else {
|
||||
defaultCheckedKeys.value = value
|
||||
}
|
||||
} else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
loadItemByCode()
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
loadRoot()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-popup-wrapper) {
|
||||
.wd-popup {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
height: 50vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 40px;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 1px;
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.cancel,
|
||||
.confrim {
|
||||
font-size: 15px;
|
||||
height: 40px;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.confrim {
|
||||
color: var(--wot-color-theme);
|
||||
}
|
||||
}
|
||||
:deep(.da-tree) {
|
||||
.da-tree-item__checkbox {
|
||||
// display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
268
JeecgUniapp-master/src/components/SelectUser/SelectUser.vue
Normal file
268
JeecgUniapp-master/src/components/SelectUser/SelectUser.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<view class="SelectUser">
|
||||
<template v-if="showType === 'form'">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view
|
||||
v-if="!!showText && !disabled"
|
||||
class="u-iconfont u-icon-close"
|
||||
@click.stop="handleClear"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="list">
|
||||
<template v-for="(item, index) in selectedUser" :key="item.id">
|
||||
<view
|
||||
v-if="maxShowUser === -1 || index < maxShowUser"
|
||||
class="user"
|
||||
@click="handleRemove(index)"
|
||||
>
|
||||
<view v-if="isDelUser" class="u-iconfont u-icon-close"></view>
|
||||
<wd-img
|
||||
v-if="getAvatar(item.avatar)"
|
||||
radius="50%"
|
||||
width="40px"
|
||||
height="40px"
|
||||
:src="getAvatar(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="name">{{ item.realname }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-if="isAddUser" class="u-iconfont u-icon-newAdd" @click="handleClick"></view>
|
||||
</view>
|
||||
</template>
|
||||
<SelectUserModal
|
||||
v-if="modalShow"
|
||||
:selected="modelValue"
|
||||
:defaultSelectedValue="defaultValue"
|
||||
:selectedUser="selectedUser"
|
||||
:modalTitle="modalTitle"
|
||||
:maxSelectCount="maxSelectCount"
|
||||
:multi="!isRadioSelection"
|
||||
:rowKey="rowKey"
|
||||
:readonlyUser="readonlyUser"
|
||||
@change="handleChange"
|
||||
@close="() => (modalShow = false)"
|
||||
></SelectUserModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import DaTree from '@/uni_modules/da-tree/index.vue'
|
||||
import { isArray, isString, isNumber } from '@/utils/is'
|
||||
import SelectUserModal from './components/SelectUserModal.vue'
|
||||
import { getPlaceholder, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUser',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
defaultValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
isRadioSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
},
|
||||
showType: {
|
||||
type: String,
|
||||
default: 'form', // form、card
|
||||
},
|
||||
// showType为card时有效
|
||||
isDelUser: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// showType为card时有效
|
||||
isAddUser: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// showType为card时有效
|
||||
maxShowUser: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
// 只读用户(添加的用户不可取消)
|
||||
readonlyUser: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'getSelectdAllData'])
|
||||
const api = {
|
||||
list: '/sys/user/list',
|
||||
}
|
||||
const showText = ref('')
|
||||
const modalShow = ref(false)
|
||||
const selectedUser = ref([])
|
||||
const getAvatar = (url) => {
|
||||
let result = getFileAccessHttpUrl(url)
|
||||
if (result.length) {
|
||||
return result
|
||||
} else {
|
||||
return defaultAvatar
|
||||
}
|
||||
}
|
||||
const handleRemove = (index?) => {
|
||||
if (props.isDelUser) {
|
||||
if (isNumber(index)) {
|
||||
selectedUser.value.splice(index, 1)
|
||||
} else {
|
||||
selectedUser.value.pop()
|
||||
}
|
||||
handleChange(selectedUser.value)
|
||||
}
|
||||
}
|
||||
// 翻译
|
||||
const transform = () => {
|
||||
let value = props.modelValue
|
||||
let len
|
||||
if (isArray(value) || isString(value)) {
|
||||
if (isArray(value)) {
|
||||
len = value.length
|
||||
value = value.join(',')
|
||||
} else {
|
||||
len = value.split(',').length
|
||||
}
|
||||
value = value.trim()
|
||||
if (value) {
|
||||
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value }
|
||||
http.get(api.list, params).then((res: any) => {
|
||||
if (res.success) {
|
||||
const records = res.result?.records ?? []
|
||||
showText.value = records.map((item) => item[props.labelKey]).join(',')
|
||||
selectedUser.value = records
|
||||
} else {
|
||||
console.log('翻译失败~')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
showText.value = ''
|
||||
}
|
||||
}
|
||||
// 打开popup
|
||||
const handleClick = () => {
|
||||
console.log('handleClick', !props.disabled)
|
||||
console.log('handleClick', props)
|
||||
if (!props.disabled) {
|
||||
modalShow.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleChange = (data) => {
|
||||
const rowkey = data.map((item) => item[props.rowKey]).join(',')
|
||||
const labelKey = data.map((item) => item[props.labelKey]).join(',')
|
||||
showText.value = labelKey
|
||||
selectedUser.value = data
|
||||
emit('update:modelValue', rowkey)
|
||||
emit('change', rowkey)
|
||||
emit('getSelectdAllData', data)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
transform()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
color: #666;
|
||||
.user {
|
||||
padding: 7px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
.u-iconfont {
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 7px;
|
||||
z-index: 1;
|
||||
/* background: white; */
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
line-height: 1;
|
||||
}
|
||||
.name {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.u-iconfont {
|
||||
font-size: 22px;
|
||||
}
|
||||
.u-icon-newAdd {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.u-icon-remove {
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout
|
||||
:navTitle="modalTitle"
|
||||
type="popup"
|
||||
navRightText="确定"
|
||||
@navRight="handleConfirm"
|
||||
@navBack="handleCancel"
|
||||
>
|
||||
<view class="wrap">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
:fixed="false"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:default-page-size="15"
|
||||
>
|
||||
<template #top>
|
||||
<wd-search
|
||||
hide-cancel
|
||||
:placeholder="search.placeholder"
|
||||
v-model="search.keyword"
|
||||
@search="handleSearch"
|
||||
@clear="handleClear"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="multi">
|
||||
<wd-checkbox-group shape="square" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<view class="list" @click="hanldeCheck(index, item[rowKey])">
|
||||
<view class="left text-gray-5">
|
||||
<wd-img
|
||||
custom-class="avatar"
|
||||
radius="50%"
|
||||
height="40"
|
||||
width="40"
|
||||
:src="getAvatar(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="subContent">
|
||||
<text>账号:{{ item.username }}</text>
|
||||
<text>姓名:{{ item.realname }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-checkbox
|
||||
ref="checkboxRef"
|
||||
:disabled="readonlyUser.includes(item[rowKey])"
|
||||
:modelValue="item[rowKey]"
|
||||
></wd-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-checkbox-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<wd-radio-group shape="dot" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<wd-cell>
|
||||
<view class="list" @click="hanldeCheck(index, item[rowKey])">
|
||||
<view class="left text-gray-5">
|
||||
<wd-img
|
||||
custom-class="avatar"
|
||||
radius="50%"
|
||||
height="40"
|
||||
width="40"
|
||||
:src="getAvatar(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="subContent">
|
||||
<text>账号:{{ item.username }}</text>
|
||||
<text>姓名:{{ item.realname }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-radio :value="item[rowKey]"></wd-radio>
|
||||
</view>
|
||||
</view>
|
||||
</wd-cell>
|
||||
</template>
|
||||
</wd-radio-group>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import { isArray, isString } from '@/utils/is'
|
||||
import { cache, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
defineOptions({
|
||||
name: 'SelectUserModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
},
|
||||
// 这是用户id
|
||||
selected: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
// 这是用户id(只是默认勾选,在弹窗中勾选)
|
||||
defaultSelectedValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
// 这是用户全数据(项包含id,username、realname)
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
// 只读用户
|
||||
readonlyUser: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const toast = useToast()
|
||||
const show = ref(true)
|
||||
const api = {
|
||||
selectUserList: '/sys/user/selectUserList',
|
||||
userlist: '/sys/user/list',
|
||||
}
|
||||
const paging = ref(null)
|
||||
const dataList = ref([])
|
||||
const checkedValue: any = ref(props.multi ? [] : '')
|
||||
const checkboxRef = ref(null)
|
||||
const search = reactive({
|
||||
keyword: '',
|
||||
placeholder: '输入姓名可搜索',
|
||||
field: 'realname',
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if (checkedValue.value.length == 0) {
|
||||
toast.warning('还没选择用户~')
|
||||
return
|
||||
}
|
||||
const result = []
|
||||
let value = checkedValue.value
|
||||
if (!Array.isArray(checkedValue.value)) {
|
||||
value = [checkedValue.value]
|
||||
}
|
||||
value.forEach((rowKey, index) => {
|
||||
const findIndex = dataList.value.findIndex((item) => item[props.rowKey] === rowKey)
|
||||
if (findIndex == -1) {
|
||||
// 传进来选中的用户可能在第二页(还没加载进来)
|
||||
const index = props.selectedUser.findIndex((item) => item[props.rowKey] === rowKey)
|
||||
if (index != -1) {
|
||||
result.push(props.selectedUser[index])
|
||||
} else {
|
||||
// 传进来defaultSelectedValue的用户可能在第二页(还没加载进来)
|
||||
if (isArray(props.defaultSelectedValue)) {
|
||||
const index = props.defaultSelectedValue.findIndex((item) => item === rowKey)
|
||||
index != -1 && result.push({ [props.rowKey]: props.defaultSelectedValue[index] })
|
||||
} else {
|
||||
props.defaultSelectedValue == rowKey && result.push({ [props.rowKey]: rowKey })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.push(dataList.value[findIndex])
|
||||
}
|
||||
})
|
||||
show.value = false
|
||||
emit('change', result)
|
||||
handleClose()
|
||||
}
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
paging.value.reload()
|
||||
}
|
||||
// 清除搜索条件
|
||||
function handleClear() {
|
||||
search.keyword = ''
|
||||
handleSearch()
|
||||
}
|
||||
const hanldeCheck = (index, username) => {
|
||||
if (props.multi) {
|
||||
if (Array.isArray(checkboxRef.value)) {
|
||||
checkboxRef.value[index].toggle()
|
||||
nextTick(() => {
|
||||
if (props.maxSelectCount) {
|
||||
if (checkedValue.value.length > props.maxSelectCount) {
|
||||
toast.warning(`最多可选择${props.maxSelectCount}个用户`)
|
||||
// 超过个数取消
|
||||
checkboxRef.value[index].toggle()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
checkedValue.value = username
|
||||
}
|
||||
}
|
||||
|
||||
const getAvatar = (url) => {
|
||||
let result = getFileAccessHttpUrl(url)
|
||||
if (result.length) {
|
||||
return result
|
||||
} else {
|
||||
return defaultAvatar
|
||||
}
|
||||
}
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
const pararms = { pageNo, pageSize, column: 'createTime', order: 'desc' }
|
||||
if (search.keyword) {
|
||||
pararms[search.field] = `*${search.keyword}*`
|
||||
}
|
||||
http
|
||||
.get(`${api.userlist}`, pararms)
|
||||
.then((res: any) => {
|
||||
if (res.success && res.result.records) {
|
||||
paging.value.complete(res.result.records ?? [])
|
||||
} else {
|
||||
paging.value.complete(false)
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
const init = () => {
|
||||
if (props.selected.length) {
|
||||
if (props.multi) {
|
||||
if (isArray(props.selected)) {
|
||||
checkedValue.value = props.selected
|
||||
} else if (isString(props.selected)) {
|
||||
checkedValue.value = props.selected.split(',')
|
||||
}
|
||||
} else {
|
||||
if (isString(props.selected)) {
|
||||
checkedValue.value = props.selected
|
||||
} else if (isArray(props.selected)) {
|
||||
checkedValue.value = props.selected.join(',')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.defaultSelectedValue.length) {
|
||||
if (props.multi) {
|
||||
if (isArray(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue
|
||||
} else if (isString(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue.split(',')
|
||||
}
|
||||
} else {
|
||||
if (isString(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue
|
||||
} else if (isArray(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-cell) {
|
||||
--wot-color-white: tranparent;
|
||||
--wot-cell-padding: 0;
|
||||
.wd-cell__wrapper {
|
||||
--wot-cell-wrapper-padding: 0;
|
||||
}
|
||||
.wd-cell__left {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.wd-checkbox-group) {
|
||||
--wot-checkbox-bg: tranparent;
|
||||
}
|
||||
:deep(.wd-radio-group) {
|
||||
--wot-radio-bg: tranparent;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
:deep(.avatar) {
|
||||
margin-right: 8px;
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.subContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
:deep(.wd-checkbox) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrap {
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.wd-popup-wrapper) {
|
||||
.wd-popup {
|
||||
top: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view class="SelectUser">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view
|
||||
v-if="!!showText && !disabled"
|
||||
class="u-iconfont u-icon-close"
|
||||
@click.stop="handleClear"
|
||||
></view>
|
||||
</view>
|
||||
<SelectUserModal
|
||||
v-if="modalShow"
|
||||
:selected="modelValue"
|
||||
:selectedUser="selectedUser"
|
||||
:modalTitle="modalTitle"
|
||||
:rowKey="rowKey"
|
||||
@change="handleChange"
|
||||
@close="() => (modalShow = false)"
|
||||
></SelectUserModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import DaTree from '@/uni_modules/da-tree/index.vue'
|
||||
import { isArray, isString, isNumber } from '@/utils/is'
|
||||
import SelectUserModal from './components/SelectUserModal.vue'
|
||||
import { getPlaceholder, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUserByDepart',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
const api = {
|
||||
list: '/sys/user/list',
|
||||
}
|
||||
const showText = ref('')
|
||||
const modalShow = ref(false)
|
||||
const selectedUser = ref([])
|
||||
// 翻译
|
||||
const transform = () => {
|
||||
let value = props.modelValue
|
||||
let len
|
||||
if (isArray(value) || isString(value)) {
|
||||
if (isArray(value)) {
|
||||
len = value.length
|
||||
value = value.join(',')
|
||||
} else {
|
||||
len = value.split(',').length
|
||||
}
|
||||
value = value.trim()
|
||||
if (value) {
|
||||
// 如果value的值在selectedUser中存在,则不请求翻译
|
||||
let isNotRequestTransform = false
|
||||
isNotRequestTransform = value
|
||||
.split(',')
|
||||
.every((value) => !!selectedUser.value.find((item) => item[props.rowKey] === value))
|
||||
if (isNotRequestTransform) {
|
||||
return
|
||||
}
|
||||
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value }
|
||||
http.get(api.list, params).then((res: any) => {
|
||||
if (res.success) {
|
||||
const records = res.result?.records ?? []
|
||||
showText.value = records.map((item) => item[props.labelKey]).join(',')
|
||||
selectedUser.value = records
|
||||
} else {
|
||||
console.log('翻译失败~')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
showText.value = ''
|
||||
}
|
||||
}
|
||||
// 打开popup
|
||||
const handleClick = () => {
|
||||
console.log('handleClick', !props.disabled)
|
||||
console.log('handleClick', props)
|
||||
if (!props.disabled) {
|
||||
modalShow.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleChange = (data) => {
|
||||
const rowkey = data.map((item) => item[props.rowKey]).join(',')
|
||||
const labelKey = data.map((item) => item[props.labelKey]).join(',')
|
||||
showText.value = labelKey
|
||||
selectedUser.value = data
|
||||
emit('update:modelValue', rowkey)
|
||||
emit('change', rowkey)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
transform()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,841 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout :navTitle="modalTitle" type="popup" @navBack="handleCancel">
|
||||
<view class="wrap">
|
||||
<view class="wrap-content">
|
||||
<wd-search
|
||||
hide-cancel
|
||||
placeholder="请输入姓名/部门"
|
||||
v-model="searchText"
|
||||
@search="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<view class="main-content">
|
||||
<scroll-view scroll-y>
|
||||
<template v-if="searchResult.depart.length || searchResult.user.length">
|
||||
<view class="search-result">
|
||||
<template v-if="searchResult.user.length">
|
||||
<view class="search-user solid-top">
|
||||
<text class="search-user-title">人员</text>
|
||||
<template v-for="item in searchResult.user" :key="item.id">
|
||||
<view
|
||||
class="search-user-item solid-top"
|
||||
@click="handleSearchUserCheck(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleSearchUserCheck(item, $event)"
|
||||
/>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="search-user-item-circle">
|
||||
<wd-img
|
||||
width="36"
|
||||
height="36px"
|
||||
v-if="item.avatar"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
/>
|
||||
</view>
|
||||
<view class="search-user-item-info">
|
||||
<view class="search-user-item-name">{{ item.realname }}</view>
|
||||
<view class="search-user-item-org">{{ item.orgCodeTxt }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="searchResult.depart.length">
|
||||
<view class="search-depart solid-top">
|
||||
<text class="search-depart-title">部门</text>
|
||||
<template v-for="item in searchResult.depart" :key="item.id">
|
||||
<view
|
||||
class="search-depart-item solid-top"
|
||||
@click="handleSearchDepartClick(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleSearchDepartCheck($event, item)"
|
||||
/>
|
||||
</view>
|
||||
<view class="search-depart-item-name">{{ item.departName }}</view>
|
||||
<wd-icon name="arrow-right" size="16px"></wd-icon>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view v-if="breadcrumb.length" class="breadcrumb-wrap solid-top">
|
||||
<Breadcrumb
|
||||
separator="/"
|
||||
:items="[
|
||||
{ title: '首页', icon: 'home' },
|
||||
...breadcrumb.map((item) => ({ title: item.departName, ...item })),
|
||||
]"
|
||||
@click="(item, index) => handleBreadcrumbClick(index === 0 ? undefined : item)"
|
||||
>
|
||||
<template #item-0="{ item }">
|
||||
<wd-icon name="home" size="18px"></wd-icon>
|
||||
</template>
|
||||
</Breadcrumb>
|
||||
</view>
|
||||
<div v-if="currentDepartUsers.length">
|
||||
<!-- 当前部门用户树 -->
|
||||
<div class="depart-users-tree solid-top">
|
||||
<div v-if="!currentDepartTree.length" class="allChecked">
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="currentDepartAllUsers"
|
||||
@change="handleAllUsers"
|
||||
>
|
||||
全选
|
||||
</wd-checkbox>
|
||||
</div>
|
||||
<template v-for="item in currentDepartUsers" :key="item.id">
|
||||
<div
|
||||
class="depart-users-tree-item solid-top"
|
||||
@click="handleDepartUsersTreeCheck(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleDepartUsersTreeCheck(item, $event)"
|
||||
/>
|
||||
</view>
|
||||
<div class="right">
|
||||
<div class="depart-users-tree-item-circle">
|
||||
<wd-img
|
||||
width="36"
|
||||
height="36px"
|
||||
v-if="item.avatar"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
/>
|
||||
</div>
|
||||
<div class="depart-users-tree-item-name">{{ item.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 部门树 -->
|
||||
<div v-if="currentDepartTree.length" class="depart-tree">
|
||||
<template v-for="item in currentDepartTree" :key="item.id">
|
||||
<div class="depart-tree-item solid-top" @click="handleDepartTreeClick(item)">
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleDepartTreeCheck($event, item)"
|
||||
/>
|
||||
</view>
|
||||
<div class="depart-tree-item-name">{{ item.departName }}</div>
|
||||
<wd-icon name="arrow-right" size="16px"></wd-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentDepartTree.length === 0 && currentDepartUsers.length === 0"
|
||||
class="no-data"
|
||||
>
|
||||
<wd-status-tip image="content" tip="暂无内容" />
|
||||
</div>
|
||||
</template>
|
||||
</scroll-view>
|
||||
<view v-if="showSelectedUser" class="selected-user">
|
||||
<SelectedUser :selectedUsers="selectedUsers" @del="handleDelUser"></SelectedUser>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wrap-footer">
|
||||
<view class="text" @click="() => (showSelectedUser = !showSelectedUser)">
|
||||
<view>已选</view>
|
||||
<view class="num">{{ selectedUsers.length }}</view>
|
||||
<view>人</view>
|
||||
<text class="tip">(查看选中用户)</text>
|
||||
</view>
|
||||
<wd-button type="primary" @click="handleConfirm">确定</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
<wd-toast :selector="selector"></wd-toast>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import { isArray, isString } from '@/utils/is'
|
||||
import { cache, getFileAccessHttpUrl, uuid } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
import SelectedUser from './SelectedUser.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUserModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
},
|
||||
// 这是用户全数据(项包含id,username、realname)
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
})
|
||||
const api = {
|
||||
selectUserList: '/sys/user/selectUserList',
|
||||
userlist: '/sys/user/list',
|
||||
queryTreeList: '/sys/sysDepart/queryTreeList',
|
||||
getTableList: '/sys/user/queryUserComponentData',
|
||||
}
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const selector = uuid()
|
||||
const toast = useToast(selector)
|
||||
const show = ref(true)
|
||||
// 搜索文本
|
||||
const searchText = ref('')
|
||||
const breadcrumb = ref<any[]>([])
|
||||
// 部门树(整颗树)
|
||||
const departTree = ref([])
|
||||
// 当前部门树
|
||||
const currentDepartTree = ref<any[]>([])
|
||||
// 选中的部门节点
|
||||
const checkedDepartIds = ref<string[]>([])
|
||||
// 当前部门用户
|
||||
const currentDepartUsers = ref([])
|
||||
// 已选用户
|
||||
const selectedUsers = ref<any[]>([])
|
||||
// 全选
|
||||
const currentDepartAllUsers = ref(false)
|
||||
// 搜索结构
|
||||
const searchResult: any = reactive({
|
||||
depart: [],
|
||||
user: [],
|
||||
})
|
||||
// 映射部门和人员的关系
|
||||
const cacheDepartUser = {}
|
||||
// 是否显示已选用户
|
||||
const showSelectedUser = ref(false)
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if (selectedUsers.value.length == 0) {
|
||||
toast.warning('还没选择用户~')
|
||||
return
|
||||
}
|
||||
show.value = false
|
||||
let result = []
|
||||
let value = selectedUsers.value
|
||||
if (!Array.isArray(selectedUsers.value)) {
|
||||
value = [selectedUsers.value]
|
||||
}
|
||||
result = value
|
||||
emit('change', result)
|
||||
handleClose()
|
||||
}
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
const getAvatar = (url) => {
|
||||
let result = getFileAccessHttpUrl(url)
|
||||
if (result.length) {
|
||||
return result
|
||||
} else {
|
||||
return defaultAvatar
|
||||
}
|
||||
}
|
||||
// 搜索人员/部门
|
||||
const handleSearch = () => {
|
||||
if (searchText.value) {
|
||||
http
|
||||
.get(`/sys/user/listAll`, {
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
realname: `*${searchText.value}*`,
|
||||
})
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
res.result.records?.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id)
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true
|
||||
} else {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
searchResult.user = res.result.records ?? []
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
searchResult.depart = getDepartByName(searchText.value) ?? []
|
||||
showSelectedUser.value = false
|
||||
} else {
|
||||
searchResult.user = []
|
||||
searchResult.depart = []
|
||||
}
|
||||
}
|
||||
// 面包屑
|
||||
const handleBreadcrumbClick = (item?) => {
|
||||
// 先清空
|
||||
currentDepartUsers.value = []
|
||||
if (item) {
|
||||
const findIndex = breadcrumb.value.findIndex((o) => o.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
breadcrumb.value = breadcrumb.value.filter((item, index) => {
|
||||
console.log(item)
|
||||
return index <= findIndex
|
||||
})
|
||||
}
|
||||
const data = getDepartTreeNodeById(item.id, departTree.value)
|
||||
currentDepartTree.value = data.children
|
||||
} else {
|
||||
// 根节点
|
||||
currentDepartTree.value = departTree.value
|
||||
breadcrumb.value = []
|
||||
}
|
||||
}
|
||||
// 点击部门树复选框触发
|
||||
const handleDepartTreeCheck = ({ value }, item) => {
|
||||
const target = { checked: value }
|
||||
if (target.checked) {
|
||||
// 选中
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
addUsers(users)
|
||||
})
|
||||
checkedDepartIds.value.push((item as any).id)
|
||||
// 检查父节点下所有子节点是否选中
|
||||
const parentItem = getDepartTreeParentById(item.id)
|
||||
if (parentItem?.children) {
|
||||
const isChildAllChecked = parentItem.children.every((item) => item.checked)
|
||||
if (isChildAllChecked) {
|
||||
parentItem.checked = true
|
||||
} else {
|
||||
parentItem.checked = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取消选中
|
||||
const findIndex = checkedDepartIds.value.findIndex((o: any) => o.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
checkedDepartIds.value.splice(findIndex, 1)
|
||||
}
|
||||
// 如果父节点是选中,则需要取消
|
||||
const parentItem = getDepartTreeParentById(item.id)
|
||||
if (parentItem) {
|
||||
parentItem.checked = false
|
||||
}
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
users.forEach((item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
// 点击部门树节点触发
|
||||
const handleDepartTreeClick = (item) => {
|
||||
breadcrumb.value = [...breadcrumb.value, item]
|
||||
if (item.children) {
|
||||
// 有子节点,则显示部门
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则子节点全部勾选
|
||||
item.children.forEach((item) => {
|
||||
item.checked = true
|
||||
})
|
||||
}
|
||||
currentDepartTree.value = item.children
|
||||
http
|
||||
.get('/sys/sysDepart/getUsersByDepartId', {
|
||||
id: item['id'],
|
||||
})
|
||||
.then((res: any) => {
|
||||
const result = res.result ?? []
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则默认勾选
|
||||
result.forEach((item) => {
|
||||
item.checked = true
|
||||
})
|
||||
}
|
||||
currentDepartUsers.value = result
|
||||
})
|
||||
} else {
|
||||
// 没有子节点,则显示用户
|
||||
currentDepartTree.value = []
|
||||
getTableList({
|
||||
departId: item['id'],
|
||||
}).then((res: any) => {
|
||||
if (res.success) {
|
||||
if (res?.result.records) {
|
||||
let checked = true
|
||||
res.result.records.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id)
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true
|
||||
} else {
|
||||
item.checked = false
|
||||
checked = false
|
||||
}
|
||||
})
|
||||
currentDepartAllUsers.value = checked
|
||||
currentDepartUsers.value = res.result.records
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// 点击部门用户树复选框触发
|
||||
const handleDepartUsersTreeCheck = (item, e?) => {
|
||||
if (e) {
|
||||
// 点击复选框时
|
||||
item.checked = e.value
|
||||
} else {
|
||||
// 点击整条数据时
|
||||
item.checked = !item.checked
|
||||
}
|
||||
if (item.checked) {
|
||||
addUsers(item)
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id)
|
||||
}
|
||||
if (item.checked == false) {
|
||||
// 有一个是false,则全选false
|
||||
currentDepartAllUsers.value = false
|
||||
}
|
||||
}
|
||||
// 全选
|
||||
const handleAllUsers = ({ target }) => {
|
||||
const { checked } = target
|
||||
if (checked) {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = true))
|
||||
addUsers(currentDepartUsers.value)
|
||||
} else {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = false))
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => {
|
||||
const userId = user.id
|
||||
const findItem = currentDepartUsers.value.find((item: any) => item.id === userId)
|
||||
if (findItem) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// 删除人员
|
||||
const handleDelUser = (item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1)
|
||||
}
|
||||
const findItem: any = currentDepartUsers.value.find((user: any) => user.id === item.id)
|
||||
if (findItem) {
|
||||
findItem.checked = false
|
||||
currentDepartAllUsers.value = false
|
||||
}
|
||||
}
|
||||
// 点击搜索用户复选框
|
||||
const handleSearchUserCheck = (item, e?) => {
|
||||
if (!e) {
|
||||
item.checked = !item.checked
|
||||
}
|
||||
if (item.checked) {
|
||||
addUsers(item)
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id)
|
||||
}
|
||||
}
|
||||
// 点击搜索部门复选框
|
||||
const handleSearchDepartCheck = (e, item) => {
|
||||
handleDepartTreeCheck(e, item)
|
||||
}
|
||||
// 点击搜索部门
|
||||
const handleSearchDepartClick = (item) => {
|
||||
searchResult.depart = []
|
||||
searchResult.user = []
|
||||
breadcrumb.value = getPathToNodeById(item.id)
|
||||
handleDepartTreeClick(item)
|
||||
}
|
||||
// 添加人员到右侧
|
||||
const addUsers = (users) => {
|
||||
let newUsers: any = []
|
||||
if (isArray(users)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = users.filter((user: any) => !selectedUsers.value.find((item) => item.id === user.id))
|
||||
} else {
|
||||
if (!selectedUsers.value.find((user) => user.id === users.id)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = [users]
|
||||
}
|
||||
}
|
||||
selectedUsers.value = [...selectedUsers.value, ...newUsers]
|
||||
const result = currentDepartUsers.value.every((item: any) => !!item.checked)
|
||||
currentDepartAllUsers.value = result
|
||||
}
|
||||
// 解析参数
|
||||
const parseParams = (params) => {
|
||||
return params
|
||||
}
|
||||
const getQueryTreeList = (params?) => {
|
||||
params = parseParams(params)
|
||||
queryTreeList({ ...params }).then((res: any) => {
|
||||
if (res.success) {
|
||||
departTree.value = res.result
|
||||
currentDepartTree.value = res.result
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 根据部门id获取用户
|
||||
const getTableList = (params) => {
|
||||
params = parseParams(params)
|
||||
return getTableListOrigin({ ...params })
|
||||
}
|
||||
const getUsersByDeptId = (id) => {
|
||||
return new Promise<any[]>((resolve) => {
|
||||
if (cacheDepartUser[id]) {
|
||||
resolve(cacheDepartUser[id])
|
||||
} else {
|
||||
getTableList({
|
||||
departId: id,
|
||||
}).then((res: any) => {
|
||||
if (res.success) {
|
||||
cacheDepartUser[id] = res?.result?.records ?? []
|
||||
if (res?.result?.records?.length) {
|
||||
resolve(res.result.records ?? [])
|
||||
}
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 根据id获取根节点到当前节点路径
|
||||
const getPathToNodeById = (id: string, tree = departTree.value, path = []): any[] => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return [...path]
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const foundPath = getPathToNodeById(id, (node as any).children, [...path, node])
|
||||
if (foundPath.length) {
|
||||
return foundPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
// 根据id获取部门树父节点数据
|
||||
const getDepartTreeParentById = (id: string, tree = departTree.value, parent = null): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return parent
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeParentById(id, (node as any).children, node)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 通过名称搜索部门支持模糊
|
||||
const getDepartByName = (name: string, tree = departTree.value): any[] => {
|
||||
const result: any[] = []
|
||||
const search = (nodes: any[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.departName?.toLowerCase().includes(name.toLowerCase())) {
|
||||
result.push(node)
|
||||
}
|
||||
if (node.children?.length) {
|
||||
search(node.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
search(tree)
|
||||
return result
|
||||
}
|
||||
// 根据id获取部门树当前节点数据
|
||||
const getDepartTreeNodeById = (id: string, tree = departTree.value): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return node
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeNodeById(id, (node as any).children)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 获取部门树列表
|
||||
const queryTreeList = (params = {}) => {
|
||||
return http.get(api.queryTreeList, { ...params })
|
||||
}
|
||||
// 根据部门id获取用户列表(包含子孙部门)
|
||||
const getTableListOrigin = (params = {}) => {
|
||||
return http.get(api.getTableList, { ...params })
|
||||
}
|
||||
// 初始化
|
||||
const init = () => {
|
||||
if (props.selectedUser.length) {
|
||||
// 编辑时,传进来已选中的数据
|
||||
selectedUsers.value = props.selectedUser
|
||||
}
|
||||
getQueryTreeList()
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrap {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.wrap-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--color-grey);
|
||||
.breadcrumb-wrap {
|
||||
background-color: #fff;
|
||||
}
|
||||
.selected-user {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.no-data {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.depart-tree {
|
||||
.depart-tree-item {
|
||||
background-color: #fff;
|
||||
padding: 0 16px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
}
|
||||
.depart-tree-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree {
|
||||
.allChecked {
|
||||
padding: 0 16px;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 12px;
|
||||
background-color: #fff;
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item {
|
||||
line-height: 50px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.depart-users-tree-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #aaa;
|
||||
overflow: hidden;
|
||||
:deep(image) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-depart {
|
||||
margin-bottom: 8px;
|
||||
.search-depart-title {
|
||||
padding-left: 16px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: var(--UI-FG-1);
|
||||
}
|
||||
.search-depart-item {
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.search-depart-item-name {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-result {
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
}
|
||||
.search-user {
|
||||
margin-bottom: 8px;
|
||||
.search-user-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
line-height: 50px;
|
||||
color: var(--UI-FG-1);
|
||||
}
|
||||
.search-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.search-user-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.search-user-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #aaa;
|
||||
}
|
||||
.search-user-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-user-item-org {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrap-footer {
|
||||
padding: 10px;
|
||||
padding-bottom: calc(constant(safe-area-inset-bottom) + 10px);
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #fff;
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.num {
|
||||
font-size: 18px;
|
||||
margin: 0 5px;
|
||||
color: var(--wot-color-theme);
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.home-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<scroll-view scroll-y class="selected-user-container solid-top">
|
||||
<view class="user-container">
|
||||
<template v-if="selectedUsers.length">
|
||||
<view class="user-item" v-for="item in selectedUsers" :key="item.id">
|
||||
<view class="user-item-avatar" @click="handledelUser(item)">
|
||||
<wd-img
|
||||
width="36px"
|
||||
height="36px"
|
||||
radius="50%"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="u-iconfont u-icon-close"></view>
|
||||
</view>
|
||||
<view class="user-item-name ellipsis">{{ item.realname }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="empty-data">
|
||||
<wd-status-tip image="content" tip="无选中用户" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
|
||||
import { useMessage, useToast } from 'wot-design-uni'
|
||||
import { useRouter } from '@/plugin/uni-mini-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { http } from '@/utils/http'
|
||||
import { useParamsStore } from '@/store/page-params'
|
||||
import { cache, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
defineOptions({
|
||||
name: 'User',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps(['selectedUsers'])
|
||||
const emit = defineEmits(['del'])
|
||||
const router = useRouter()
|
||||
const paramsStore = useParamsStore()
|
||||
const userStore = useUserStore()
|
||||
const toast = useToast()
|
||||
const message = useMessage()
|
||||
// 删除用户
|
||||
const handledelUser = (item: any) => {
|
||||
emit('del', item)
|
||||
}
|
||||
// 获取用户名的最后一个字符
|
||||
const getLastCharacterOfName = (name: string): string => {
|
||||
if (!name) return ''
|
||||
return name.charAt(name.length - 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
//
|
||||
.selected-user-container {
|
||||
height: 100%;
|
||||
}
|
||||
.user-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
padding: 10px;
|
||||
min-height: 100%;
|
||||
.user-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 20%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
.user-item-avatar {
|
||||
position: relative;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: -5px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.empty-data {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
386
JeecgUniapp-master/src/components/TreeSelect/TreeSelect.vue
Normal file
386
JeecgUniapp-master/src/components/TreeSelect/TreeSelect.vue
Normal file
@@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<view class="TreeSelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup position="bottom" v-model="popupShow">
|
||||
<view class="content">
|
||||
<view class="operation">
|
||||
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
|
||||
<view class="confrim" @click.stop="confirm">确定</view>
|
||||
</view>
|
||||
<scroll-view class="flex-1" scroll-y>
|
||||
<DaTree
|
||||
v-if="popupShow"
|
||||
:data="treeData"
|
||||
labelField="title"
|
||||
valueField="key"
|
||||
loadMode
|
||||
:showCheckbox="multiple"
|
||||
:showRadioIcon="false"
|
||||
:checkStrictly="true"
|
||||
:loadApi="asyncLoadTreeData"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
@change="handleTreeChange"
|
||||
></DaTree>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import DaTree from '@/uni_modules/da-tree/index.vue'
|
||||
import { isArray } from '@/utils/is'
|
||||
defineOptions({
|
||||
name: 'TreeSelect',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
},
|
||||
dict: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
pidValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
pidField: {
|
||||
type: String,
|
||||
default: 'pid',
|
||||
},
|
||||
hasChildField: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
converIsLeafVal: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hiddenNodeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// url: {
|
||||
// type: String,
|
||||
// default: '',
|
||||
// },
|
||||
// params: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// },
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const toast = useToast()
|
||||
const api = {
|
||||
loadTreeData: '/sys/dict/loadTreeData',
|
||||
view: '/sys/dict/loadDictItem/',
|
||||
}
|
||||
const showText = ref('')
|
||||
const popupShow = ref(false)
|
||||
const treeData = ref<any[]>([])
|
||||
const treeValue = ref([])
|
||||
const tableName = ref<any>('')
|
||||
const text = ref<any>('')
|
||||
const code = ref<any>('')
|
||||
const attrs = useAttrs()
|
||||
const defaultCheckedKeys: any = ref(props.multiple ? [] : '')
|
||||
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
popupShow.value = true
|
||||
}
|
||||
}
|
||||
const cancel = () => {
|
||||
popupShow.value = false
|
||||
}
|
||||
const confirm = () => {
|
||||
const titles = treeValue.value.map((item) => item.title)
|
||||
const keys = treeValue.value.map((item) => item.key).join(',')
|
||||
showText.value = titles.join(',')
|
||||
popupShow.value = false
|
||||
emit('update:modelValue', keys)
|
||||
emit('change', keys)
|
||||
}
|
||||
const handleTreeChange = (value, record) => {
|
||||
const { originItem, checkedStatus } = record
|
||||
const { key, title } = originItem
|
||||
if (checkedStatus) {
|
||||
// 选中
|
||||
if (props.multiple) {
|
||||
treeValue.value.push({ key, title })
|
||||
} else {
|
||||
treeValue.value = [{ key, title }]
|
||||
}
|
||||
} else {
|
||||
// 取消
|
||||
if (props.multiple) {
|
||||
const findIndex = treeValue.value.findIndex((item) => item.key == key)
|
||||
if (findIndex != -1) {
|
||||
treeValue.value.splice(findIndex, 1)
|
||||
}
|
||||
} else {
|
||||
treeValue.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
const transformField = (result) => {
|
||||
for (let i of result) {
|
||||
i.value = i.key
|
||||
if (i.leaf == false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf == true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// 异步加载
|
||||
const asyncLoadTreeData = ({ originItem }) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
let param = {
|
||||
pid: originItem.key,
|
||||
pidField: props.pidField,
|
||||
hasChildField: props.hasChildField,
|
||||
converIsLeafVal: props.converIsLeafVal,
|
||||
condition: props.condition,
|
||||
tableName: unref(tableName),
|
||||
text: unref(text),
|
||||
code: unref(code),
|
||||
}
|
||||
http
|
||||
.get(api.loadTreeData, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
transformField(result)
|
||||
resolve(result)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
.catch((err) => resolve(null))
|
||||
})
|
||||
}
|
||||
// 加载根节点
|
||||
function loadRoot() {
|
||||
let param = {
|
||||
pid: props.pidValue,
|
||||
pidField: props.pidField,
|
||||
hasChildField: props.hasChildField,
|
||||
condition: props.condition,
|
||||
converIsLeafVal: props.converIsLeafVal,
|
||||
tableName: unref(tableName),
|
||||
text: unref(text),
|
||||
code: unref(code),
|
||||
}
|
||||
http
|
||||
.get(api.loadTreeData, param)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result } = res
|
||||
if (result && result.length > 0) {
|
||||
transformField(result)
|
||||
handleHiddenNode(result)
|
||||
treeData.value = result
|
||||
}
|
||||
} else {
|
||||
toast.warning('自定义树组件根节点数据加载失败~')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.warning('自定义树组件根节点数据加载失败~')
|
||||
})
|
||||
}
|
||||
// 翻译input内的值
|
||||
function loadItemByCode() {
|
||||
let value = props.modelValue
|
||||
if(value){
|
||||
if (isArray(props.modelValue)) {
|
||||
// @ts-ignore
|
||||
value = value.join()
|
||||
}
|
||||
if (value === treeData.value.map((item) => item.key).join(',')) {
|
||||
// 说明是刚选完,内部已有翻译。不需要再请求
|
||||
return
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(`${api.view}${props.dict}`, { key: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.join(',')
|
||||
if (props.multiple) {
|
||||
defaultCheckedKeys.value = typeof value === 'string' ? value.split(',') : [value]
|
||||
} else {
|
||||
defaultCheckedKeys.value = value
|
||||
}
|
||||
} else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
const initDictInfo = () => {
|
||||
let arr = props.dict?.split(',')
|
||||
tableName.value = arr[0]
|
||||
text.value = arr[1]
|
||||
code.value = arr[2]
|
||||
}
|
||||
const handleHiddenNode = (data) => {
|
||||
if (props.hiddenNodeKey && data?.length) {
|
||||
for (let i = 0, len = data.length; i < len; i++) {
|
||||
const item = data[i]
|
||||
if (item.key == props.hiddenNodeKey) {
|
||||
data.splice(i, 1)
|
||||
i--
|
||||
len--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const validateProp = () => {
|
||||
let mycondition = props.condition
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!mycondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
let test = JSON.parse(mycondition)
|
||||
if (typeof test == 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
toast.error('组件TreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error('组件TreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => loadItemByCode(),
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
watch(
|
||||
() => props.dict,
|
||||
() => {
|
||||
initDictInfo()
|
||||
loadRoot()
|
||||
},
|
||||
)
|
||||
watch(
|
||||
() => props.hiddenNodeKey,
|
||||
() => {
|
||||
if (treeData.value?.length && props.hiddenNodeKey) {
|
||||
handleHiddenNode(treeData.value)
|
||||
treeData.value = [...treeData.value]
|
||||
}
|
||||
},
|
||||
)
|
||||
// 初始化
|
||||
validateProp().then(() => {
|
||||
initDictInfo()
|
||||
loadRoot()
|
||||
loadItemByCode()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-popup-wrapper) {
|
||||
.wd-popup {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
height: 50vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 40px;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 1px;
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.cancel,
|
||||
.confrim {
|
||||
font-size: 15px;
|
||||
height: 40px;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.confrim {
|
||||
color: var(--wot-color-theme);
|
||||
}
|
||||
}
|
||||
:deep(.da-tree) {
|
||||
.da-tree-item__checkbox {
|
||||
// display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
JeecgUniapp-master/src/components/components.d.ts
vendored
Normal file
22
JeecgUniapp-master/src/components/components.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BottomOperate: typeof import('./BottomOperate/BottomOperate.vue')['default']
|
||||
Breadcrumb: typeof import('./Breadcrumb/Breadcrumb.vue')['default']
|
||||
CategorySelect: typeof import('./CategorySelect/CategorySelect.vue')['default']
|
||||
DateTime: typeof import('./DateTime/DateTime.vue')['default']
|
||||
Grid: typeof import('./Grid/Grid.vue')['default']
|
||||
ImgPreview: typeof import('./ImgPreview/ImgPreview.vue')['default']
|
||||
LFile: typeof import('./LFile/LFile.vue')['default']
|
||||
PageLayout: typeof import('./PageLayout/PageLayout.vue')['default']
|
||||
Popup: typeof import('./Popup/Popup.vue')['default']
|
||||
PopupDict: typeof import('./PopupDict/PopupDict.vue')['default']
|
||||
ProgressMap: typeof import('./ProgressMap/ProgressMap.vue')['default']
|
||||
RightConditionFilter: typeof import('./RightConditionFilter/RightConditionFilter.vue')['default']
|
||||
SelectDept: typeof import('./SelectDept/SelectDept.vue')['default']
|
||||
SelectUser: typeof import('./SelectUser/SelectUser.vue')['default']
|
||||
SelectUserByDepart: typeof import('./SelectUserByDepart/SelectUserByDepart.vue')['default']
|
||||
TreeSelect: typeof import('./TreeSelect/TreeSelect.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
169
JeecgUniapp-master/src/components/online/FormProperty.ts
Normal file
169
JeecgUniapp-master/src/components/online/FormProperty.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
// 定义 FormProperty 函数
|
||||
const FormProperty = (propertyId, formSchema, required = []) => {
|
||||
// 初始化私有属性
|
||||
const _propertyId = propertyId;
|
||||
const _formSchem = formSchema;
|
||||
const _required = required;
|
||||
|
||||
// 定义 formSchema 的 getter 方法
|
||||
const getFormSchema = () => {
|
||||
return _formSchem || {};
|
||||
};
|
||||
|
||||
// 定义 key 的 getter 方法
|
||||
const getKey = () => {
|
||||
return _propertyId;
|
||||
};
|
||||
|
||||
// 定义 type 的 getter 方法
|
||||
const getType = () => {
|
||||
return getFormSchema().view;
|
||||
};
|
||||
|
||||
// 定义 disabled 的 getter 方法
|
||||
const getDisabled = () => {
|
||||
if (_formSchem && _formSchem.ui && _formSchem.ui.widgetattrs && _formSchem.ui.widgetattrs.disabled === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 定义 label 的 getter 方法
|
||||
const getLabel = () => {
|
||||
const schema = getFormSchema();
|
||||
return schema.title || getKey();
|
||||
};
|
||||
|
||||
// 定义 placeholder 的 getter 方法
|
||||
const getPlaceholder = () => {
|
||||
const viewType = getType();
|
||||
const label = getLabel();
|
||||
if (viewType.indexOf('date') >= 0 || viewType.indexOf('select') >= 0 || viewType.indexOf('list') >= 0) {
|
||||
return "请选择" + label;
|
||||
} else if (viewType.indexOf('upload') >= 0 || viewType.indexOf('file') >= 0 || viewType.indexOf('image') >= 0) {
|
||||
return "请上传" + label;
|
||||
} else {
|
||||
return "请输入" + label;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义 dictStr 的 getter 方法
|
||||
const getDictStr = () => {
|
||||
const viewType = getType();
|
||||
if (viewType === 'sel_search') {
|
||||
const schema = getFormSchema();
|
||||
return schema.dictTable + ',' + schema.dictText + ',' + schema.dictCode;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 定义 listSource 的 getter 方法
|
||||
const getListSource = () => {
|
||||
const schema = getFormSchema();
|
||||
if (!schema.enum) {
|
||||
return [];
|
||||
}
|
||||
const arr = [...schema.enum];
|
||||
for (let a = 0; a < arr.length; a++) {
|
||||
if (!arr[a].label) {
|
||||
arr[a].label = arr[a].text;
|
||||
}
|
||||
if (schema.type === 'number') {
|
||||
arr[a].value = parseInt(arr[a].value);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
// 定义 popupCode 的 getter 方法
|
||||
const getPopupCode = () => {
|
||||
return getFormSchema().code;
|
||||
};
|
||||
|
||||
// 定义 dest 的 getter 方法
|
||||
const getDest = () => {
|
||||
return getFormSchema().destFields;
|
||||
};
|
||||
|
||||
// 定义 ogn 的 getter 方法
|
||||
const getOgn = () => {
|
||||
return getFormSchema().orgFields;
|
||||
};
|
||||
|
||||
// 定义 rules 的 getter 方法
|
||||
const getRules = () => {
|
||||
const rules = [];
|
||||
const isRequired = _required?.includes(getKey()) ?? false;
|
||||
if (isRequired) {
|
||||
let msg = getLabel() + '为必填项';
|
||||
rules.push({ required: true, message: msg });
|
||||
}
|
||||
let viewType = getType();
|
||||
if ('list' === viewType || 'markdown' === viewType || 'pca' === viewType) {
|
||||
return rules;
|
||||
}
|
||||
if (viewType.indexOf('upload') >= 0 || viewType.indexOf('file') >= 0 || viewType.indexOf('image') >= 0) {
|
||||
return rules;
|
||||
}
|
||||
|
||||
const schema = getFormSchema();
|
||||
if (schema.pattern) {
|
||||
if (schema.pattern === 'only') {
|
||||
// 这里 checkOnlyMethod 未定义,需要根据实际情况补充
|
||||
rules.push({ validator: () => {} });
|
||||
} else if (schema.pattern === 'z') {
|
||||
if (schema.type === 'number' || schema.type === 'integer') {
|
||||
// 这里 onlyInteger 未定义,需要根据实际情况处理
|
||||
} else {
|
||||
rules.push({ pattern: '^-?[1-9]\\d*$', message: '请输入整数' });
|
||||
}
|
||||
} else {
|
||||
let msg = getLabel() + '校验未通过';
|
||||
rules.push({ pattern: schema.pattern, message: msg });
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
};
|
||||
|
||||
// 返回包含所有 getter 方法的对象
|
||||
return {
|
||||
get formSchema() {
|
||||
return getFormSchema();
|
||||
},
|
||||
get key() {
|
||||
return getKey();
|
||||
},
|
||||
get type() {
|
||||
return getType();
|
||||
},
|
||||
get disabled() {
|
||||
return getDisabled();
|
||||
},
|
||||
get label() {
|
||||
return getLabel();
|
||||
},
|
||||
get placeholder() {
|
||||
return getPlaceholder();
|
||||
},
|
||||
get dictStr() {
|
||||
return getDictStr();
|
||||
},
|
||||
get listSource() {
|
||||
return getListSource();
|
||||
},
|
||||
get popupCode() {
|
||||
return getPopupCode();
|
||||
},
|
||||
get dest() {
|
||||
return getDest();
|
||||
},
|
||||
get ogn() {
|
||||
return getOgn();
|
||||
},
|
||||
get rules() {
|
||||
return getRules();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default FormProperty;
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view class="onlineTableCell">
|
||||
<!--图片-->
|
||||
<template v-if="column?.type === 'image'">
|
||||
<template v-if="record[column.key]">
|
||||
<wd-img
|
||||
width="30"
|
||||
height="30"
|
||||
:src="getFirstImg(record[column.key])"
|
||||
@click="handleClickImg"
|
||||
></wd-img>
|
||||
<ImgPreview
|
||||
v-if="imgPreview.show"
|
||||
:urls="imgPreview.urls"
|
||||
@close="() => (imgPreview.show = false)"
|
||||
></ImgPreview>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text>无图片</text>
|
||||
</template>
|
||||
</template>
|
||||
<!--下载-->
|
||||
<template v-else-if="column?.type === 'file'">
|
||||
<template v-if="record[column.key]">
|
||||
<wd-button @click="handleDownload(record[column.key])">下载</wd-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text>无文件</text>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="['markdown', 'umeditor'].includes(column?.type)">
|
||||
<rich-text :nodes="record[column.key]"></rich-text>
|
||||
</template>
|
||||
<template v-else-if="column?.type === 'pca'">
|
||||
<text class="ellipsis-2">{{ getPcaText(record[column.key])}}</text>
|
||||
</template>
|
||||
<template v-else-if="['datetime', 'date'].includes(column?.type)">
|
||||
<text class="ellipsis-2">{{ getFormatDate(record[column.key], column) }}</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="ellipsis-2">{{ renderVal(record, column)}}</text>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getFormatDate, filterMultiDictText } from '@/common/uitls'
|
||||
import { isString } from '@/utils/is'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import { getAreaTextByCode } from '@/common/areaData/Area'
|
||||
defineOptions({
|
||||
name: 'OnlineSubTableCell',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
columnsInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
column: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
record: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
console.log("根据配置动态加载表单record ",props.record );
|
||||
const imgPreview = ref({
|
||||
show: false,
|
||||
urls: [],
|
||||
})
|
||||
// 下载
|
||||
const handleDownload = (text) => {
|
||||
uni.downloadFile({
|
||||
url: text,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('下载成功')
|
||||
console.log(res);
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
// 省市区
|
||||
const getPcaText = (code) => {
|
||||
if (!code) {
|
||||
return ''
|
||||
}
|
||||
return getAreaTextByCode(code)
|
||||
}
|
||||
// 列表只显示第一张图
|
||||
const getFirstImg = (text) => {
|
||||
if (isString(text)) {
|
||||
var imgs = text.split(',')
|
||||
return getFileAccessHttpUrl(imgs[0])
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// 点击图时
|
||||
const handleClickImg = () => {
|
||||
imgPreview.value.show = true
|
||||
}
|
||||
// 渲染值
|
||||
const renderVal = (record, column) => {
|
||||
const { type , key } = column
|
||||
let text = record[key]
|
||||
if (['date', 'Date'].includes(type)) {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
if (text.length > 10) {
|
||||
return text.substring(0, 10)
|
||||
}
|
||||
return text
|
||||
} else if (['popup_dict'].includes(type)) {
|
||||
const dict = record[key + '_dictText']
|
||||
if (dict != undefined) {
|
||||
return record[key + '_dictText']
|
||||
}
|
||||
return text
|
||||
}
|
||||
//字典值翻譯
|
||||
if(props.columnsInfo?.dictOptions && props.columnsInfo?.dictOptions[key]){
|
||||
return filterMultiDictText(props.columnsInfo.dictOptions[key], text + '')
|
||||
}
|
||||
return text
|
||||
}
|
||||
// 初始化
|
||||
const init = () => {
|
||||
const field = props.column.dataIndex
|
||||
if (props.column?.customRender === 'imgSlot') {
|
||||
const text = props.record[field]
|
||||
if (isString(text)) {
|
||||
imgPreview.value.urls = text.split(',').map((item) => getFileAccessHttpUrl(item))
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-button) {
|
||||
--wot-button-medium-height: 30px;
|
||||
--wot-button-medium-fs: 12px;
|
||||
--wot-button-medium-padding: 8px;
|
||||
&.is-medium.is-round {
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,867 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout
|
||||
:navTitle="navTitle"
|
||||
type="popup"
|
||||
navRightText="确定"
|
||||
@navRight="handleConfirm"
|
||||
@navBack="handleCancel"
|
||||
>
|
||||
<scroll-view scroll-y class="wrap">
|
||||
<wd-form ref="form" :model="formData">
|
||||
<wd-cell-group class="mb-14px" border>
|
||||
<view
|
||||
class="onlineLoader-form"
|
||||
v-for="(item, index) in rootProperties"
|
||||
:key="index"
|
||||
:class="{ 'mt-14px': index % 2 == 0 }"
|
||||
>
|
||||
<!-- 图片 -->
|
||||
<wd-cell
|
||||
v-if="item.type == 'image'"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<online-image
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
></online-image>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 文件 -->
|
||||
<wd-cell
|
||||
v-else-if="item.type == 'file'"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<online-file
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<online-file-custom
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file-custom>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</wd-cell>
|
||||
<!-- 开关 -->
|
||||
<wd-cell
|
||||
v-else-if="['switch', 'checkbox'].includes(item.type)"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
center
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<wd-switch
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
size="18px"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:active-value="switchOpt(item.formSchema?.extendOption, 0)"
|
||||
:inactive-value="switchOpt(item.formSchema?.extendOption, 1)"
|
||||
/>
|
||||
</view>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 日期时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'datetime'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
startTime="1949-01-01 00:00:00"
|
||||
endTime="2050-01-01 00:00:00"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
format="HH:mm:ss"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 日期 -->
|
||||
<online-date
|
||||
v-else-if="item.type === 'date'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:type="getDateExtendType(item.formSchema)"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-date>
|
||||
|
||||
<!-- 时间 -->
|
||||
<online-time
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-time>
|
||||
|
||||
<!-- 下拉选择 -->
|
||||
<online-select
|
||||
v-else-if="item.type === 'list' || item.type === 'sel_search'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:type="item.type"
|
||||
:dict="item.options"
|
||||
:dictStr="item.dictStr"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-select>
|
||||
|
||||
<!-- checkbox -->
|
||||
<online-checkbox
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:name="item.key"
|
||||
:type="item.type"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:dict="item.options"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-checkbox>
|
||||
|
||||
<!-- radio -->
|
||||
<online-radio
|
||||
v-else-if="item.type === 'radio'"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:type="item.type"
|
||||
:dict="item.options"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-radio>
|
||||
|
||||
<!-- 下拉多选 -->
|
||||
<online-multi
|
||||
v-else-if="item.type === 'list_multi'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:dict="item.options"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-multi>
|
||||
|
||||
<!-- 省市区 -->
|
||||
<online-pca
|
||||
v-else-if="item.type === 'pca'"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
></online-pca>
|
||||
|
||||
<!-- 数字框 小数 -->
|
||||
<wd-input
|
||||
v-else-if="
|
||||
item.type === 'number' && (!item.onlyInteger || item.onlyInteger == false)
|
||||
"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
inputMode="decimal"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
/>
|
||||
|
||||
<!-- 数字框 整数 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'number' && item.onlyInteger === true"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model="formData[item.key]"
|
||||
inputMode="numeric"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
/>
|
||||
|
||||
<!-- 多行文本 -->
|
||||
<wd-textarea
|
||||
v-else-if="['textarea', 'markdown', 'umeditor'].includes(item.type)"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model="formData[item.key]"
|
||||
clearable
|
||||
:maxlength="300"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
/>
|
||||
<!-- 密码输入框 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'password'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="componentDisabled(item)"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
show-password
|
||||
/>
|
||||
<!-- popup字典 -->
|
||||
<PopupDict
|
||||
v-else-if="item.type === 'popup_dict'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:multi="item.formSchema.popupMulti"
|
||||
:dictCode="`${item.formSchema.code},${item.formSchema['destFields']},${item.formSchema['orgFields']}`"
|
||||
></PopupDict>
|
||||
<!-- popup -->
|
||||
<Popup
|
||||
v-else-if="item.type === 'popup'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:multi="!!item.formSchema?.popupMulti"
|
||||
:code="`${item.formSchema.popupCode}`"
|
||||
:setFieldsValue="setFieldsValue"
|
||||
:fieldConfig="getPopupFieldConfig(item)"
|
||||
></Popup>
|
||||
<!-- 关联记录 -->
|
||||
<online-popup-link-record
|
||||
v-else-if="item.type === 'link_table'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model:formSchema="item.formSchema"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
@selected="linkRecordChange"
|
||||
></online-popup-link-record>
|
||||
<!-- 他表字段 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'link_table_field'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="true"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
/>
|
||||
<!-- 用户选择 -->
|
||||
<select-user
|
||||
v-else-if="item.type === 'sel_user'"
|
||||
:label-width="labelWidth"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></select-user>
|
||||
|
||||
<!-- 部门选择 -->
|
||||
<select-dept
|
||||
v-else-if="item.type === 'sel_depart'"
|
||||
:label-width="labelWidth"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
labelKey="departName"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></select-dept>
|
||||
<!-- 分类字典树 -->
|
||||
<CategorySelect
|
||||
v-else-if="item.type === 'cat_tree'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:pid="`${item.formSchema.pidValue}`"
|
||||
></CategorySelect>
|
||||
<!-- 自定义树 -->
|
||||
<TreeSelect
|
||||
v-else-if="item.type === 'sel_tree'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:dict="`${item.formSchema.dict}`"
|
||||
:pidField="`${item.formSchema.pidField}`"
|
||||
:pidValue="`${item.formSchema.pidValue}`"
|
||||
:hasChildField="`${item.formSchema.hasChildField}`"
|
||||
></TreeSelect>
|
||||
<!-- 普通输入框 -->
|
||||
<wd-input
|
||||
v-else-if="item.type !== 'hidden'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="componentDisabled(item)"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</scroll-view>
|
||||
</PageLayout>
|
||||
</wd-popup>
|
||||
<wd-toast></wd-toast>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
|
||||
import OnlineMulti from '@/components/online/view/online-multi.vue'
|
||||
import SelectUser from '@/components/SelectUser/SelectUser.vue'
|
||||
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
|
||||
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||
import OnlineDate from '@/components/online/view/online-date.vue'
|
||||
import OnlineTime from '@/components/online/view/online-time.vue'
|
||||
import OnlineRadio from '@/components/online/view/online-radio.vue'
|
||||
import SelectDept from '@/components/SelectDept/SelectDept.vue'
|
||||
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||
import OnlinePca from '../view/online-pca.vue'
|
||||
import { loadOneFieldDefVal } from '../defaultVal'
|
||||
import { isArray, isNumber, isString } from '@/utils/is'
|
||||
import { formatDate } from '@/common/uitls'
|
||||
import { deepClone } from 'wot-design-uni/components/common/util'
|
||||
import { duplicateCheck } from '@/service/api'
|
||||
import { isObject } from '@/common/is'
|
||||
defineOptions({
|
||||
name: 'OnlineSubformPopup',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
schema: {
|
||||
type: Object,
|
||||
default: () => ({}) as any,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const toast = useToast()
|
||||
const show = ref(true)
|
||||
//表数据Data
|
||||
const formData = ref<any>({})
|
||||
//是否填值规则字段
|
||||
const hasFillRuleFields = ref([])
|
||||
//是否必填字段
|
||||
const hasRequiredFields = ref([])
|
||||
//字段属性
|
||||
const rootProperties = ref<any>([])
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名
|
||||
const tableName = ref('')
|
||||
//数据Id
|
||||
const dataId = ref('')
|
||||
//是否已加载
|
||||
const loaded = ref(false)
|
||||
//是否编辑
|
||||
const isUpdate = ref(false)
|
||||
//临时数据
|
||||
const tempData = ref({})
|
||||
// 标题宽度
|
||||
const labelWidth = computed(() => {
|
||||
return '100px'
|
||||
})
|
||||
// 导航标题
|
||||
const get4Label = computed(() => {
|
||||
return (lable) => {
|
||||
return `${lable && lable.length > 4 ? lable.substring(0, 4) : lable}:`
|
||||
}
|
||||
})
|
||||
// 导航标题
|
||||
const navTitle = computed(() => {
|
||||
if (!props.title || props.title.length === 0) {
|
||||
return tableTxt.value
|
||||
} else {
|
||||
return props.title
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取日期控件的扩展类型
|
||||
* @param formSchema
|
||||
* @returns {string}
|
||||
*/
|
||||
const getDateExtendType = (formSchema: any) => {
|
||||
if (formSchema.fieldExtendJson) {
|
||||
let fieldExtendJson = JSON.parse(formSchema.fieldExtendJson)
|
||||
let mapField = {
|
||||
month: 'year-month',
|
||||
year: 'year',
|
||||
quarter: 'quarter',
|
||||
week: 'week',
|
||||
day: 'date',
|
||||
}
|
||||
return fieldExtendJson?.picker && mapField[fieldExtendJson?.picker]
|
||||
? mapField[fieldExtendJson?.picker]
|
||||
: 'date'
|
||||
}
|
||||
return 'date'
|
||||
}
|
||||
/**
|
||||
* 判断是否选中
|
||||
* @param opts
|
||||
* @param value
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
const isChecked = (opts: any, value: any) => {
|
||||
return opts && opts.length > 0 ? value === opts[0] : false
|
||||
}
|
||||
/**
|
||||
* 开关选项
|
||||
* @param opts
|
||||
* @param value
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
const switchOpt = (opts: any, index: any) => {
|
||||
const options = Array.isArray(opts) && opts.length > 0 ? opts : ['Y', 'N']
|
||||
return options[index] + ''
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns {*|boolean}
|
||||
*/
|
||||
const componentDisabled = (item: any) => {
|
||||
if (props.disabled === true) {
|
||||
return true
|
||||
}
|
||||
return item.disabled
|
||||
}
|
||||
/**
|
||||
* 获取扩展字段
|
||||
* @param item
|
||||
* @param field
|
||||
*/
|
||||
const getFieldExtendJson = (item, field) => {
|
||||
let json = item.formSchema.fieldExtendJson ?? '{}'
|
||||
if (isString(json) && json.trim().length === 0) {
|
||||
json = '{}'
|
||||
}
|
||||
json = JSON.parse(json)
|
||||
const result = json[field]
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 判断字段是否必填
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldRequired = (item: any) => {
|
||||
return item?.key && hasRequiredFields.value.includes(item.key)
|
||||
}
|
||||
/**
|
||||
* 关联记录同步修改他表字段
|
||||
* @param linkRecord
|
||||
* @param key
|
||||
*/
|
||||
const linkRecordChange = (linkRecord, key) => {
|
||||
let linkFieldArr = rootProperties.value.filter(
|
||||
(item) => item.type === 'link_table_field' && item?.formSchema?.dictTable == key,
|
||||
)
|
||||
console.log('linkRecordChange****》》》》', linkRecordChange)
|
||||
linkFieldArr.forEach((field) => {
|
||||
let value = linkRecord.map((record) => record[field.formSchema.dictText]).join(',')
|
||||
nextTick(() => {
|
||||
formData.value[field.key] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 校验字段
|
||||
* @param values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldCheck = async (values: any) => {
|
||||
// 校验字段
|
||||
let flag = false
|
||||
console.log('一对多子表rootProperties.value', rootProperties.value)
|
||||
console.log('一对多子表values', values)
|
||||
for (const item of rootProperties.value) {
|
||||
// 校验提示
|
||||
const tip = (msg) => {
|
||||
// 提示校验未通过
|
||||
toast.warning(`${msg}`)
|
||||
flag = true
|
||||
}
|
||||
// 校验必填
|
||||
if (fieldRequired(item) && !values[item.key]) {
|
||||
tip(`${item.label}不能为空!`)
|
||||
break
|
||||
}
|
||||
// 校验正则
|
||||
let pattern = item?.formSchema?.pattern || item?.pattern
|
||||
if (pattern) {
|
||||
//可能存在是一个对象的情况,把正则表达式赋值一下
|
||||
if (isObject(pattern)) {
|
||||
pattern = pattern?.pattern
|
||||
}
|
||||
if (pattern == 'only') {
|
||||
const res: any = await duplicateCheck({
|
||||
tableName: tableName.value,
|
||||
fieldName: item.key,
|
||||
fieldVal: values[item.key],
|
||||
dataId: dataId.value,
|
||||
})
|
||||
if (!res.success) {
|
||||
tip(`${item.label} ${res.message}`)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const regex = compilePattern(pattern)
|
||||
if (values[item.key] && !regex.test(values[item.key])) {
|
||||
let errorInfo = item?.formSchema?.errorInfo || '格式不正确!'
|
||||
tip(`${item.label}${errorInfo}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
/**
|
||||
* 省市区获取最后一位
|
||||
* @param value
|
||||
*/
|
||||
function pcaValue(value) {
|
||||
if (value.includes(',')) {
|
||||
const parts = value.split(',')
|
||||
if (parts.length >= 3) {
|
||||
// 如果包含至少两个逗号,截取第三部分并去掉最后一位
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
// 如果不包含逗号或不符合条件,返回原值
|
||||
return value
|
||||
}
|
||||
/**
|
||||
* 处理多选字段
|
||||
* @param value
|
||||
*/
|
||||
function handleMultiOrDateField() {
|
||||
let finalData = deepClone(formData.value)
|
||||
//日期字段
|
||||
let dateFieldArr = rootProperties.value.filter(
|
||||
(item) => item.type === 'date' || item.type === 'datetime',
|
||||
)
|
||||
//省市区字段
|
||||
let pcaArr = rootProperties.value.filter((item) => item.type === 'pca')
|
||||
finalData = Object.keys(finalData).reduce((acc, key) => {
|
||||
let value = finalData[key]
|
||||
//省市区获取最后一位
|
||||
if (value && pcaArr.length > 0 && pcaArr.map((item) => item.key).includes(key)) {
|
||||
value = isArray(value) ? value[2] : pcaValue(value)
|
||||
}
|
||||
//是数组的就转换成字符串
|
||||
if (value && isArray(value)) {
|
||||
value = value.join(',')
|
||||
}
|
||||
//时间戳类型的日期转具体格式字符串
|
||||
if (dateFieldArr.length > 0) {
|
||||
const dateField = dateFieldArr.find((obj) => obj.key === key)
|
||||
if (dateField) {
|
||||
value =
|
||||
value && isNumber(value)
|
||||
? formatDate(
|
||||
value,
|
||||
dateField.type === 'datetime' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd',
|
||||
)
|
||||
: value
|
||||
}
|
||||
}
|
||||
acc[key] = value
|
||||
return acc
|
||||
}, {})
|
||||
return finalData
|
||||
}
|
||||
//打开弹窗前
|
||||
function beforeOpen(row, edit) {
|
||||
isUpdate.value = edit
|
||||
tempData.value = row
|
||||
formData.value = { ...row }
|
||||
}
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
const handleConfirm = async () => {
|
||||
// 判断字段必填和正则
|
||||
if (await fieldCheck(formData.value)) {
|
||||
return
|
||||
}
|
||||
// 处理特殊字段
|
||||
let finalData = await handleMultiOrDateField()
|
||||
emit('change', finalData, isUpdate.value)
|
||||
//关闭弹窗
|
||||
handleClose()
|
||||
}
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
loaded.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
/**
|
||||
* 设置pop组件数值
|
||||
* @param data
|
||||
*/
|
||||
const setFieldsValue = (data) => {
|
||||
formData.value = { ...formData.value, ...data }
|
||||
}
|
||||
/**
|
||||
* popup组件配置
|
||||
* @param item
|
||||
*/
|
||||
const getPopupFieldConfig = (item) => {
|
||||
const { formSchema } = item
|
||||
const { destFields = '', orgFields = '' } = formSchema
|
||||
const result = orgFields.split(',').map((oField, index) => {
|
||||
return {
|
||||
source: oField,
|
||||
target: destFields.split(',')[index],
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
//监听配置变化
|
||||
watchEffect(() => {
|
||||
props.schema && !loaded.value && loadTableInfo(props.schema)
|
||||
})
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(row) {
|
||||
dataId.value = row?.id
|
||||
formData.value = deepClone(row)
|
||||
}
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(formSchema: any) {
|
||||
console.log('===子表many加载表单数据 schema===', formSchema)
|
||||
createRootProperties(formSchema)
|
||||
tableTxt.value = formSchema?.describe
|
||||
if (isUpdate.value) {
|
||||
initFormData(tempData.value)
|
||||
} else {
|
||||
// 新增页面处理表单默认值
|
||||
handleDefaultValue()
|
||||
}
|
||||
loaded.value = true
|
||||
}
|
||||
/**
|
||||
* 创建根属性
|
||||
* @param formSchema
|
||||
*/
|
||||
function createRootProperties(formSchema: any) {
|
||||
console.log('===子表配置项 formSchema===', formSchema)
|
||||
formData.value = {}
|
||||
hasRequiredFields.value = []
|
||||
hasFillRuleFields.value = formSchema?.hasFillRuleFields ?? []
|
||||
tableName.value = formSchema.key
|
||||
const properties = formSchema.columns
|
||||
let rootProps = []
|
||||
properties.forEach((item) => {
|
||||
if (item?.key && item?.key != 'action') {
|
||||
formData.value[item?.key] = ''
|
||||
let patternInfo = null
|
||||
if (item?.validateRules && item?.validateRules.length > 0) {
|
||||
//字段是否必填
|
||||
let isRequired = item?.validateRules.some((valid) => valid?.required)
|
||||
isRequired && hasRequiredFields.value.push(item.key)
|
||||
//正则校验其他配置信息
|
||||
patternInfo = item?.validateRules.find((valid) => valid.pattern)
|
||||
if (patternInfo && patternInfo?.pattern && patternInfo.pattern == '*') {
|
||||
hasRequiredFields.value.push(item.key)
|
||||
}
|
||||
}
|
||||
rootProps.push({
|
||||
...item,
|
||||
type: item?.view || item.type,
|
||||
label: item.title,
|
||||
pattern: patternInfo,
|
||||
formSchema: { ...item, pattern: patternInfo?.pattern, errorInfo: patternInfo?.message },
|
||||
})
|
||||
}
|
||||
})
|
||||
rootProperties.value = [...rootProps]
|
||||
console.log('--子表 rootProperties--', rootProps)
|
||||
}
|
||||
/**
|
||||
* 获取默认值
|
||||
*/
|
||||
function handleDefaultValue() {
|
||||
rootProperties.value.forEach((item) => {
|
||||
let field = item.key
|
||||
let { defaultValue, type, fieldDefaultValue } = item.formSchema
|
||||
let defVal = defaultValue || fieldDefaultValue
|
||||
loadOneFieldDefVal(defVal, type, (value) => {
|
||||
formData.value[field] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 根据规则表将 value 转换为对应的正则表达式
|
||||
* @param {string} value 输入的值(如 "n6-16", "s6-18", "*6-16", "url", "e" 等)
|
||||
* @returns {RegExp} 返回对应的正则表达式
|
||||
*/
|
||||
function compilePattern(value) {
|
||||
let builtIn = patternArr.some((item) => item.value == value)
|
||||
if (!builtIn) {
|
||||
return new RegExp(value)
|
||||
}
|
||||
// 数字类型(n)
|
||||
if (value.startsWith('n')) {
|
||||
const rangeMatch = value.match(/^n(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^\\d{${min},${max}}$`)
|
||||
}
|
||||
return /^\d+$/ // 默认:纯数字
|
||||
}
|
||||
|
||||
// 字母类型(s)
|
||||
if (value.startsWith('s')) {
|
||||
const rangeMatch = value.match(/^s(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^[a-zA-Z]{${min},${max}}$`)
|
||||
}
|
||||
return /^[a-zA-Z]+$/ // 默认:纯字母
|
||||
}
|
||||
|
||||
// 任意字符(*)
|
||||
if (value.startsWith('*')) {
|
||||
if (value === '*') return /^.+$/ // 非空
|
||||
const rangeMatch = value.match(/^\*(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^.{${min},${max}}$`)
|
||||
}
|
||||
}
|
||||
|
||||
// 其他预定义规则
|
||||
const predefinedPatterns = {
|
||||
url: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i,
|
||||
e: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
||||
m: /^1[3-9]\d{9}$/,
|
||||
p: /^[1-9]\d{5}$/,
|
||||
z: /^-?\d+$/,
|
||||
money: /^-?\d+(\.\d{1,2})?$/,
|
||||
}
|
||||
|
||||
return predefinedPatterns[value] || new RegExp(`^${escapeRegExp(value)}$`)
|
||||
}
|
||||
|
||||
// 辅助函数:转义正则特殊字符
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
const patternArr = [
|
||||
{ label: '6到16位数字', value: 'n6-16' },
|
||||
{ label: '6到18位字母', value: 's6-18' },
|
||||
{ label: '6到16位任意字符', value: '*6-16' },
|
||||
{ label: '网址', value: 'url' },
|
||||
{ label: '电子邮件', value: 'e' },
|
||||
{ label: '手机号码', value: 'm' },
|
||||
{ label: '邮政编码', value: 'p' },
|
||||
{ label: '字母', value: 's' },
|
||||
{ label: '数字', value: 'n' },
|
||||
{ label: '整数', value: 'z' },
|
||||
{ label: '非空', value: '*' },
|
||||
{ label: '金额', value: 'money' },
|
||||
]
|
||||
|
||||
defineExpose({
|
||||
beforeOpen,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
30
JeecgUniapp-master/src/components/online/customExpression.ts
Normal file
30
JeecgUniapp-master/src/components/online/customExpression.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* 这里填写用户自定义的表达式
|
||||
* 可用在Online表单的默认值表达式中使用
|
||||
* 需要外部使用的变量或方法一定要 export,否则无法识别
|
||||
* 示例:
|
||||
* export const name = '张三'; // const 是常量
|
||||
* export let age = 17; // 看情况 export const 还是 let ,两者都可正常使用
|
||||
* export function content(arg) { // export 方法,可传参数,使用时要加括号,值一定要return回去,可以返回Promise
|
||||
* return 'content' + arg;
|
||||
* }
|
||||
* export const address = (arg) => content(arg) + ' | 北京市'; // export 箭头函数也可以
|
||||
*
|
||||
*/
|
||||
|
||||
/** 字段默认值官方示例:获取地址 */
|
||||
export function demoFieldDefVal_getAddress(arg) {
|
||||
if (!arg) {
|
||||
arg = '朝阳区';
|
||||
}
|
||||
return `北京市 ${arg}`;
|
||||
}
|
||||
|
||||
/** 自定义JS函数示例 */
|
||||
export function sayHi(name) {
|
||||
if (!name) {
|
||||
name = '张三';
|
||||
}
|
||||
return `您好,我叫: ${name}`;
|
||||
}
|
||||
302
JeecgUniapp-master/src/components/online/defaultVal.ts
Normal file
302
JeecgUniapp-master/src/components/online/defaultVal.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
|
||||
// 移动端不支持自定义表达式设置的默认值
|
||||
import {http} from "@/utils/http";
|
||||
import {useUserStore} from "@/store";
|
||||
import * as CustomExpression from './customExpression';
|
||||
import dayjs from "dayjs";
|
||||
|
||||
// 获取所有用户自定义表达式的Key
|
||||
const ceKeys = Object.keys(CustomExpression)
|
||||
// 将key用逗号拼接,可以拼接成方法参数,例:a,b,c --> function(a,b,c){}
|
||||
const ceJoin = ceKeys.join(',')
|
||||
// 将用户自定义的表达式按key的顺序放到数组中,可以使用 apply 传递给方法直接调用
|
||||
const $CE$ = ceKeys.map(key => CustomExpression[key])
|
||||
|
||||
/** 普通规则表达式 #{...} */
|
||||
const normalRegExp = /#{([^}]+)?}/g
|
||||
/** 用户自定义规则表达式 {{...}} */
|
||||
const customRegExp = /{{([^}]+)?}}/g
|
||||
/** 填值规则表达式 ${...} */
|
||||
const fillRuleRegExp = /\${([^}]+)?}/g
|
||||
|
||||
/** action 类型 */
|
||||
export const ACTION_TYPES = { ADD: 'add', EDIT: 'edit', DETAIL: 'detail', RELOAD: 'reload' }
|
||||
|
||||
/**
|
||||
* 获取单个字段的默认值-通过回调函数返回值
|
||||
* @param {Object} defVal
|
||||
* @param {Object} type
|
||||
* @param {Object} callback
|
||||
*/
|
||||
export async function loadOneFieldDefVal(defVal, type, callback) {
|
||||
if(hasEffectiveValue(defVal)){
|
||||
let value = await handleDefaultValue(defVal, ACTION_TYPES.ADD, {});
|
||||
if ('number' === type && value) {
|
||||
value = Number.parseFloat(value)
|
||||
}
|
||||
callback(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的值是不是有效的
|
||||
*/
|
||||
function hasEffectiveValue(val) {
|
||||
if(val || val === 0){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认值
|
||||
* @param {Object} defVal
|
||||
* @param {Object} action
|
||||
* @param {Object} getFormData
|
||||
*/
|
||||
async function handleDefaultValue(defVal, action, getFormData) {
|
||||
if (defVal != null) {
|
||||
// 检查类型,如果类型错误则不继续运行
|
||||
if (checkExpressionType(defVal)) {
|
||||
let value = await getDefaultValue(defVal, action, getFormData)
|
||||
if (value != null) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return defVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载form组件默认值
|
||||
* @param form Form对象
|
||||
* @param properties 字段配置
|
||||
* @param action 操作类型(ACTION_TYPES),除填值规则外,其他表达式只在add下才执行
|
||||
* @param getFormData 获取数据的方法,用于填值规则向后台传值
|
||||
*/
|
||||
export function loadFieldDefVal({ form, properties, action, getFormData }) {
|
||||
if (Array.isArray(properties) && properties.length > 0) {
|
||||
properties.forEach(async prop => {
|
||||
let { defVal, type } = prop._formSchem
|
||||
// key取值错误导致 树形表 表单默认值未生效 【online】树列表不支持控件默认值表达式配置 (博威)
|
||||
let key = prop.key
|
||||
// 2021年5月21日 Tree类型表单,系统编码不生效。【issues/I3NR39】
|
||||
if (!key) {
|
||||
key = prop._propertyId
|
||||
}
|
||||
eachHandler(key, defVal, action, (value) => {
|
||||
// 处理数字类型,如果type=number并且value有值
|
||||
if ('number' === type && value) {
|
||||
// parseFloat() 可以直接处理字符串、整数、小数、null和undefined,
|
||||
// 非数字类型直接返回NaN,不必担心报错
|
||||
value = Number.parseFloat(value)
|
||||
}
|
||||
form.setFieldsValue({ [key]: value })
|
||||
}, getFormData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载JEditableTable组件默认值 */
|
||||
export function loadFieldDefValForSubTable({ subForms, subTable, row, action, getFormData }) {
|
||||
if (subTable && Array.isArray(subTable.columns) && subTable.columns.length > 0) {
|
||||
subTable.columns.forEach(async column => {
|
||||
let { key, fieldDefaultValue: defVal } = column
|
||||
eachHandler(key, defVal, action, (value) => {
|
||||
if (subForms.form) {
|
||||
subForms.form.setFieldsValue({ [key]: value })
|
||||
} else {
|
||||
// update-begin---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑-----------
|
||||
let v = [{rowKey: row.id, values: {[key]: value}}];
|
||||
(subForms.jvt || subForms.jet).setValues(v)
|
||||
// update-end---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑------------
|
||||
}
|
||||
}, getFormData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function eachHandler(key, defVal, action, callback, getFormData) {
|
||||
if (defVal != null) {
|
||||
// 检查类型,如果类型错误则不继续运行
|
||||
if (checkExpressionType(defVal)) {
|
||||
let value = await getDefaultValue(defVal, action, getFormData)
|
||||
if (value != null) {
|
||||
callback(value)
|
||||
}
|
||||
} else {
|
||||
// 不合法的表达式直接返回不解析
|
||||
callback(defVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查表达式类型是否合法,规则:
|
||||
* 1、填值规则表达式不能和其他表达式混用
|
||||
* 2、每次只能填写一个填值规则表达式
|
||||
* 3、普通表达式和用户自定义表达式可以混用
|
||||
*/
|
||||
export function checkExpressionType(defVal) {
|
||||
// 获取各个表达式的数量
|
||||
let normalCount = 0, customCount = 0, fillRuleCount = 0
|
||||
defVal.replace(fillRuleRegExp, () => fillRuleCount++)
|
||||
if (fillRuleCount > 1) {
|
||||
logWarn(`表达式[${defVal}]不合法:只能同时填写一个填值规则表达式!`)
|
||||
return false
|
||||
}
|
||||
defVal.replace(normalRegExp, () => normalCount++)
|
||||
defVal.replace(customRegExp, () => customCount++)
|
||||
// 除填值规则外其他规则的数量
|
||||
let fillRuleOtherCount = normalCount + customCount
|
||||
if (fillRuleCount > 0 && fillRuleOtherCount > 0) {
|
||||
logWarn(`表达式[${defVal}]不合法:填值规则表达式不能和其他表达式混用!`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 获取所有匹配的表达式 */
|
||||
function getRegExpMap(text, exp) {
|
||||
let map = new Map()
|
||||
if(text && text.length>0){
|
||||
text.replace(exp, function (match, param, offset, string) {
|
||||
map.set(match, param.trim())
|
||||
return match
|
||||
})
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/** 获取默认值,可以执行表达式,可以执行用户自定义方法,可以异步获取用户信息等 */
|
||||
async function getDefaultValue(defVal, action, getFormData) {
|
||||
// 只有在 add 和 reload 模式下才执行填值规则
|
||||
if (action === ACTION_TYPES.ADD || action === ACTION_TYPES.RELOAD) {
|
||||
// 判断是否是填值规则表达式,如果是就执行填值规则
|
||||
if (fillRuleRegExp.test(defVal)) {
|
||||
return await executeRegExp(defVal, fillRuleRegExp, executeFillRuleExpression, [getFormData])
|
||||
}
|
||||
}
|
||||
// 只有在 add 模式下才执行其他表达式
|
||||
if (action === ACTION_TYPES.ADD) {
|
||||
// 获取并替换所有常规表达式
|
||||
defVal = await executeRegExp(defVal, normalRegExp, executeNormalExpression)
|
||||
// 获取并替换所有用户自定义表达式
|
||||
defVal = await executeRegExp(defVal, customRegExp, executeCustomExpression)
|
||||
return defVal
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function executeRegExp(defVal, regExp, execFun, otherParams = []) {
|
||||
let map = getRegExpMap(defVal, regExp)
|
||||
// @ts-ignore
|
||||
for (let origin of map.keys()) {
|
||||
let exp = map.get(origin)
|
||||
let result = await execFun.apply(null, [exp, origin, ...otherParams])
|
||||
// 如果只有一个表达式,那么就不替换(因为一旦替换,类型就会被转成String),直接返回执行结果,保证返回的类型不变
|
||||
if (origin === defVal) {
|
||||
return result
|
||||
}
|
||||
defVal = replaceAll(defVal, origin, result)
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
/** 执行【普通表达式】#{xxx} */
|
||||
async function executeNormalExpression(expression, origin) {
|
||||
switch (expression) {
|
||||
case 'date':
|
||||
return dayjs().format('YYYY-MM-DD');
|
||||
case 'time':
|
||||
return dayjs().format('HH:mm:ss');
|
||||
case 'datetime':
|
||||
return dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
default:
|
||||
// 获取当前登录用户的信息
|
||||
let result = getUserInfoByExpression(expression)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
// 没有符合条件的表达式,返回原始值
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据表达式获取相应的用户信息 */
|
||||
function getUserInfoByExpression(expression) {
|
||||
let userInfo:any = useUserStore().userInfo;
|
||||
if (userInfo) {
|
||||
switch (expression) {
|
||||
case 'sysUserId':
|
||||
return userInfo.id
|
||||
// 当前登录用户登录账号
|
||||
case 'sysUserCode':
|
||||
return userInfo.username
|
||||
// 当前登录用户真实名称
|
||||
case 'sysUserName':
|
||||
return userInfo.realname
|
||||
// 当前登录用户部门编号
|
||||
case 'sysOrgCode':
|
||||
return userInfo.orgCode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* 2023-09-04
|
||||
* liaozhiyang
|
||||
* 用new Function替换eval
|
||||
*/
|
||||
function _eval(str: string) {
|
||||
return new Function(`return ${str}`)();
|
||||
}
|
||||
/** 执行【用户自定义表达式】 {{xxx}} 移动端不支持 */
|
||||
async function executeCustomExpression(expression, origin) {
|
||||
// 利用 eval 生成一个方法,这个方法的参数就是用户自定义的所有的表达式
|
||||
let fn = _eval(`(function (${ceJoin}){ return ${expression} })`);
|
||||
try {
|
||||
// 然后调用这个方法,并把表达式传递进去,从而完成表达式的执行
|
||||
return fn.apply(null, $CE$)
|
||||
} catch (e) {
|
||||
// 执行失败,输出错误并返回原始值
|
||||
logWarn(e)
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
||||
/** 执行【填值规则表达式】 ${xxx} */
|
||||
async function executeFillRuleExpression(expression, origin, getFormData) {
|
||||
let url = `/sys/fillRule/executeRuleByCode/${expression}`
|
||||
let formData = {}
|
||||
if (typeof getFormData === 'function') {
|
||||
formData = getFormData()
|
||||
}
|
||||
let res:any = await http.put(url, formData)
|
||||
let { success, message, result } = res;
|
||||
console.log(success, message, result)
|
||||
if (success) {
|
||||
return result
|
||||
} else {
|
||||
logError(`填值规则(${expression})执行失败:${message}`)
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
||||
function logWarn(message) {
|
||||
console.warn('[loadFieldDefVal]:', message)
|
||||
}
|
||||
|
||||
function logError(message) {
|
||||
console.error('[loadFieldDefVal]:', message)
|
||||
}
|
||||
|
||||
function replaceAll(text, checker, replacer) {
|
||||
let lastText = text
|
||||
text = text.replace(checker, replacer)
|
||||
if (lastText !== text) {
|
||||
return replaceAll(text, checker, replacer)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
1139
JeecgUniapp-master/src/components/online/online-loader.vue
Normal file
1139
JeecgUniapp-master/src/components/online/online-loader.vue
Normal file
File diff suppressed because it is too large
Load Diff
245
JeecgUniapp-master/src/components/online/online-sub-many.vue
Normal file
245
JeecgUniapp-master/src/components/online/online-sub-many.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<view class="online-sub-many-container">
|
||||
<wd-button :disabled="componentDisabled" customClass="add" icon="add" size="small" @click="handleAdd">新增</wd-button>
|
||||
<wd-table :data="dataList" height="400px">
|
||||
<template v-for="(col,index) in columns" :key="index">
|
||||
<wd-table-col :prop="col.key" :label="col.title" align="center" :fixed="col.key == 'action'">
|
||||
<template #value="value" v-if="col.key == 'action'">
|
||||
<view class="custom-action">
|
||||
<wd-button size="small" type="icon" icon="edit" @click="handleEdit(value)"></wd-button>
|
||||
<wd-button size="small" type="icon" icon="delete" style="color:rgba(255,0,0,0.5)" @click="handleDel(value)"></wd-button>
|
||||
</view>
|
||||
</template>
|
||||
<template #value="{ row, index }" :key="index" v-else>
|
||||
<online-sub-table-cell
|
||||
:columnsInfo="columnsInfo"
|
||||
:record="row"
|
||||
:column="col"
|
||||
></online-sub-table-cell>
|
||||
</template>
|
||||
</wd-table-col>
|
||||
</template>
|
||||
</wd-table>
|
||||
</view>
|
||||
<online-subform-popup
|
||||
v-if="popupShow"
|
||||
ref="subFormPop"
|
||||
:title="tableTxt"
|
||||
:schema="formSchema"
|
||||
:disabled="disabled"
|
||||
@close="handleClose"
|
||||
@change="handleChange"
|
||||
></online-subform-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useMessage, useToast} from 'wot-design-uni';
|
||||
import OnlineSubformPopup from './components/online-subform-popup.vue'
|
||||
import OnlineSubTableCell from './components/online-sub-table-cell.vue'
|
||||
import { http } from '@/utils/http'
|
||||
import {deepClone} from "wot-design-uni/components/common/util";
|
||||
defineOptions({
|
||||
name: 'online-sub-many',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
tableInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({} as any)
|
||||
},
|
||||
dataInfo: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ([])
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
}
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['back', 'success'])
|
||||
//提示
|
||||
const toast = useToast()
|
||||
//pop弹窗
|
||||
const subFormPop = ref(null)
|
||||
//表ID
|
||||
const tableId = ref('')
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名称
|
||||
const tableName = ref('')
|
||||
//列信息
|
||||
const columns = ref([])
|
||||
//表单配置信息
|
||||
const formSchema = ref([])
|
||||
//弹窗显示
|
||||
const popupShow = ref(false)
|
||||
//数据列表
|
||||
const dataList = ref([])
|
||||
//数据列表
|
||||
const editIndex = ref(null)
|
||||
//列信息
|
||||
const columnsInfo = ref({})
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns {*|boolean}
|
||||
*/
|
||||
const componentDisabled = computed(()=>{
|
||||
console.log("一对多many组件",props.disabled)
|
||||
console.log("一对多many组件",props.showFooter)
|
||||
if (props.disabled === true || !props.showFooter) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
//监听配置修改
|
||||
watchEffect(()=>{
|
||||
props.tableInfo && loadTableInfo(props.tableInfo)
|
||||
})
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(tableInfo: any){
|
||||
console.log('===子表many加载表单数据 schema===', tableInfo)
|
||||
formSchema.value = deepClone(tableInfo)
|
||||
tableId.value = tableInfo.id;
|
||||
tableName.value = tableInfo.key;
|
||||
tableTxt.value = tableInfo.describe;
|
||||
columns.value = tableInfo?.columns || [];
|
||||
http
|
||||
.get(`/online/cgform/api/getColumns/${tableId.value}`)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
if (res.result?.columns?.length) {
|
||||
columnsInfo.value = res.result;
|
||||
}
|
||||
}
|
||||
})
|
||||
let hasAction = columns.value.some(item=>item.key == 'action');
|
||||
!hasAction && columns.value.unshift({title:'操作', key:'action'});
|
||||
console.log("根据配置动态加载表单columns.value ",columns.value );
|
||||
if (props.edit === true) {
|
||||
initFormData(props.dataInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleDel({index}){
|
||||
if(componentDisabled.value){
|
||||
return;
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
dataList.value.splice(index,1);
|
||||
toast.success('删除成功')
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleAdd(){
|
||||
popupShow.value = true;
|
||||
nextTick(()=>{
|
||||
subFormPop.value.beforeOpen(null,false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 编辑
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleEdit({row,index}){
|
||||
if(!componentDisabled.value){
|
||||
editIndex.value = index;
|
||||
popupShow.value = true;
|
||||
nextTick(()=>{
|
||||
subFormPop.value.beforeOpen(row,true)
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 关闭弹窗
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleClose(){
|
||||
popupShow.value = false;
|
||||
}
|
||||
/**
|
||||
* 表单内容修改
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleChange(record,isUpdate){
|
||||
if(isUpdate){
|
||||
dataList.value[unref(editIndex)] = record;
|
||||
}else{
|
||||
dataList.value.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交前处理
|
||||
*/
|
||||
function beforeSubmit(){
|
||||
console.log("一对多beforeSubmit",dataList.value);
|
||||
return {status:true,data:dataList.value}
|
||||
}
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(data) {
|
||||
if (data && data.length>0)
|
||||
dataList.value = deepClone(data)
|
||||
}
|
||||
defineExpose({
|
||||
beforeSubmit,
|
||||
initFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.online-sub-many-container {
|
||||
min-height: 300px;
|
||||
.custom-action{
|
||||
display: flex;
|
||||
}
|
||||
:deep(.add) {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
808
JeecgUniapp-master/src/components/online/online-sub-one.vue
Normal file
808
JeecgUniapp-master/src/components/online/online-sub-one.vue
Normal file
@@ -0,0 +1,808 @@
|
||||
<template>
|
||||
<view class="online-sub-one-container">
|
||||
<view class="form-container">
|
||||
<wd-form ref="form" :model="formData">
|
||||
<wd-cell-group border>
|
||||
<view
|
||||
class="onlineLoader-form"
|
||||
v-for="(item, index) in rootProperties"
|
||||
:key="index"
|
||||
:class="{ 'mt-14px': index % 2 == 0, 'mt-2px': index == 0 }"
|
||||
>
|
||||
<!-- 图片 -->
|
||||
<wd-cell
|
||||
v-if="item.type == 'image'"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<online-image
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
></online-image>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 文件 -->
|
||||
<wd-cell
|
||||
v-else-if="item.type == 'file'"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<online-file
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<online-file-custom
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file-custom>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 日期时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'datetime'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
startTime="1949-01-01 00:00:00"
|
||||
endTime="2050-01-01 00:00:00"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
format="HH:mm:ss"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 日期 -->
|
||||
<online-date
|
||||
v-else-if="item.type === 'date'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:type="getDateExtendType(item.formSchema)"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-date>
|
||||
|
||||
<!-- 时间 -->
|
||||
<online-time
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-time>
|
||||
|
||||
<!-- 下拉选择 -->
|
||||
<online-select
|
||||
v-else-if="item.type === 'list' || item.type === 'sel_search'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:type="item.type"
|
||||
:dict="item.listSource"
|
||||
:dictStr="item.dictStr"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></online-select>
|
||||
|
||||
<!-- checkbox -->
|
||||
<online-checkbox
|
||||
v-else-if="item.type === 'checkbox'"
|
||||
:name="item.key"
|
||||
:type="item.type"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:dict="item.listSource"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-checkbox>
|
||||
|
||||
<!-- radio -->
|
||||
<online-radio
|
||||
v-else-if="item.type === 'radio'"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:type="item.type"
|
||||
:dict="item.listSource"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-radio>
|
||||
|
||||
<!-- 下拉多选 -->
|
||||
<online-multi
|
||||
v-else-if="item.type === 'list_multi'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:name="item.key"
|
||||
:dict="item.listSource"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></online-multi>
|
||||
|
||||
<!-- 省市区 -->
|
||||
<online-pca
|
||||
v-else-if="item.type === 'pca'"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
></online-pca>
|
||||
|
||||
<!-- 数字框 小数 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'number' && (!item.onlyInteger || item.onlyInteger == false)"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
inputMode="decimal"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.rules"
|
||||
/>
|
||||
|
||||
<!-- 数字框 整数 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'number' && item.onlyInteger === true"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model="formData[item.key]"
|
||||
inputMode="numeric"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.rules"
|
||||
/>
|
||||
|
||||
<!-- 开关 -->
|
||||
<wd-cell
|
||||
v-else-if="item.type == 'switch'"
|
||||
:name="item.key"
|
||||
:title="get4Label(item.label)"
|
||||
:title-width="labelWidth"
|
||||
center
|
||||
:required="fieldRequired(item)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<wd-switch
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
size="18px"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:active-value="switchOpt(item.formSchema?.extendOption, 0)"
|
||||
:inactive-value="switchOpt(item.formSchema?.extendOption, 1)"
|
||||
/>
|
||||
</view>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 多行文本 -->
|
||||
<wd-textarea
|
||||
v-else-if="['textarea', 'markdown', 'umeditor'].includes(item.type)"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model="formData[item.key]"
|
||||
clearable
|
||||
:maxlength="300"
|
||||
:disabled="componentDisabled(item)"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.rules"
|
||||
/>
|
||||
<!-- 密码输入框 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'password'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="componentDisabled(item)"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.rules"
|
||||
show-password
|
||||
/>
|
||||
<!-- popup字典 -->
|
||||
<PopupDict
|
||||
v-else-if="item.type === 'popup_dict'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:multi="item.formSchema.popupMulti"
|
||||
:dictCode="`${item.formSchema.code},${item.formSchema['destFields']},${item.formSchema['orgFields']}`"
|
||||
></PopupDict>
|
||||
<!-- popup -->
|
||||
<Popup
|
||||
v-else-if="item.type === 'popup'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:multi="item.formSchema.popupMulti"
|
||||
:code="`${item.formSchema.code}`"
|
||||
:setFieldsValue="setFieldsValue"
|
||||
:fieldConfig="getPopupFieldConfig(item)"
|
||||
></Popup>
|
||||
<!-- 关联记录 -->
|
||||
<online-popup-link-record
|
||||
v-else-if="item.type === 'link_table'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
v-model:formSchema="item.formSchema"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model:value="formData[item.key]"
|
||||
@selected="linkRecordChange"
|
||||
></online-popup-link-record>
|
||||
<!-- 他表字段 -->
|
||||
<wd-input
|
||||
v-else-if="item.type === 'link_table_field'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="true"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
/>
|
||||
<!-- 用户选择 -->
|
||||
<select-user
|
||||
v-else-if="item.type === 'sel_user'"
|
||||
:label-width="labelWidth"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></select-user>
|
||||
|
||||
<!-- 部门选择 -->
|
||||
<select-dept
|
||||
v-else-if="item.type === 'sel_depart'"
|
||||
:label-width="labelWidth"
|
||||
:name="item.key"
|
||||
:label="get4Label(item.label)"
|
||||
labelKey="departName"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
></select-dept>
|
||||
<!-- 分类字典树 -->
|
||||
<CategorySelect
|
||||
v-else-if="item.type === 'cat_tree'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:pid="`${item.formSchema.pidValue}`"
|
||||
></CategorySelect>
|
||||
<!-- 自定义树 -->
|
||||
<TreeSelect
|
||||
v-else-if="item.type === 'sel_tree'"
|
||||
:label-width="labelWidth"
|
||||
:label="get4Label(item.label)"
|
||||
:disabled="componentDisabled(item)"
|
||||
:required="fieldRequired(item)"
|
||||
v-model="formData[item.key]"
|
||||
:dict="`${item.formSchema.dict}`"
|
||||
:pidField="`${item.formSchema.pidField}`"
|
||||
:pidValue="`${item.formSchema.pidValue}`"
|
||||
:hasChildField="`${item.formSchema.hasChildField}`"
|
||||
></TreeSelect>
|
||||
<!-- 普通输入框 -->
|
||||
<wd-input
|
||||
v-else-if="item.type !== 'hidden'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="componentDisabled(item)"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.rules"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</view>
|
||||
</view>
|
||||
<wd-toast></wd-toast>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormProperty from './FormProperty'
|
||||
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||
import OnlineTime from '@/components/online/view/online-time.vue'
|
||||
import OnlineDate from '@/components/online/view/online-date.vue'
|
||||
import OnlineRadio from '@/components/online/view/online-radio.vue'
|
||||
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
|
||||
import OnlineMulti from '@/components/online/view/online-multi.vue'
|
||||
import OnlinePca from './view/online-pca.vue'
|
||||
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
|
||||
import SelectDept from '@/components/SelectDept/SelectDept.vue'
|
||||
import SelectUser from '@/components/SelectUser/SelectUser.vue'
|
||||
import { loadOneFieldDefVal } from './defaultVal'
|
||||
import { useToast, useMessage, useNotify } from 'wot-design-uni'
|
||||
import { deepClone } from 'wot-design-uni/components/common/util'
|
||||
import { isArray, isNumber, isString } from '@/utils/is'
|
||||
import { formatDate } from '@/common/uitls'
|
||||
import { duplicateCheck } from '@/service/api'
|
||||
defineOptions({
|
||||
name: 'online-sub-one',
|
||||
options: {
|
||||
// apply-shared:当前页面样式会影响到子组件样式.(小程序)
|
||||
// shared:当前页面样式影响到子组件,子组件样式也会影响到当前页面.(小程序)
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
tableInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}) as any,
|
||||
},
|
||||
dataInfo: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['back', 'success'])
|
||||
//定义提示
|
||||
const toast = useToast()
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名称
|
||||
const tableName = ref('')
|
||||
//表数据Data
|
||||
const formData = ref<any>({})
|
||||
//是否填值规则字段
|
||||
const hasFillRuleFields = ref('')
|
||||
//是否必填字段
|
||||
const hasRequiredFields = ref([])
|
||||
//字段属性
|
||||
const rootProperties = ref<any>([])
|
||||
//表单数据ID
|
||||
const formDataId = ref('')
|
||||
//表单数据ID
|
||||
const loaded = ref(false)
|
||||
// 标题宽度
|
||||
const labelWidth = computed(() => {
|
||||
return '100px'
|
||||
})
|
||||
// 导航标题
|
||||
const get4Label = computed(() => {
|
||||
return (lable) => {
|
||||
return `${lable && lable.length > 4 ? lable.substring(0, 4) : lable}:`
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取日期控件的扩展类型
|
||||
* @param formSchema
|
||||
* @returns {string}
|
||||
*/
|
||||
const getDateExtendType = (formSchema: any) => {
|
||||
if (formSchema?.fieldExtendJson) {
|
||||
let fieldExtendJson = JSON.parse(formSchema.fieldExtendJson)
|
||||
let mapField = {
|
||||
month: 'year-month',
|
||||
year: 'year',
|
||||
quarter: 'quarter',
|
||||
week: 'week',
|
||||
day: 'date',
|
||||
}
|
||||
return fieldExtendJson?.picker && mapField[fieldExtendJson?.picker]
|
||||
? mapField[fieldExtendJson?.picker]
|
||||
: 'date'
|
||||
}
|
||||
return 'date'
|
||||
}
|
||||
/**
|
||||
* 判断是否选中
|
||||
* @param opts
|
||||
* @param value
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
const isChecked = (opts: any, value: any) => {
|
||||
return opts && opts.length > 0 ? value === opts[0] : false
|
||||
}
|
||||
/**
|
||||
* 开关选项
|
||||
* @param opts
|
||||
* @param value
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
const switchOpt = (opts: any, index: any) => {
|
||||
const options = Array.isArray(opts) && opts.length > 0 ? opts : ['Y', 'N']
|
||||
return options[index] + ''
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns {*|boolean}
|
||||
*/
|
||||
const componentDisabled = (item: any) => {
|
||||
if (props.disabled === true || !props.showFooter) {
|
||||
return true
|
||||
}
|
||||
return item.disabled
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字段是否必填
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldRequired = (item: any) => {
|
||||
return item?.key && hasRequiredFields.value.includes(item.key)
|
||||
}
|
||||
/**
|
||||
* 获取扩展字段
|
||||
* @param item
|
||||
* @param field
|
||||
*/
|
||||
const getFieldExtendJson = (item, field) => {
|
||||
let json = item.formSchema.fieldExtendJson ?? '{}'
|
||||
if (isString(json) && json.trim().length === 0) {
|
||||
json = '{}'
|
||||
}
|
||||
json = JSON.parse(json)
|
||||
const result = json[field]
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 关联记录同步修改他表字段
|
||||
* @param linkRecord
|
||||
* @param key
|
||||
*/
|
||||
const linkRecordChange = (linkRecord, key) => {
|
||||
let linkFieldArr = rootProperties.value.filter(
|
||||
(item) => item.type === 'link_table_field' && item?.formSchema?.dictTable == key,
|
||||
)
|
||||
linkFieldArr.forEach((field) => {
|
||||
let value = linkRecord.map((record) => record[field.formSchema.dictText]).join(',')
|
||||
nextTick(() => {
|
||||
formData.value[field.key] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
//监听配置修改
|
||||
watchEffect(() => {
|
||||
props.tableInfo && !loaded.value && loadTableInfo(props.tableInfo)
|
||||
})
|
||||
/**
|
||||
* 省市区获取最后一位
|
||||
* @param value
|
||||
*/
|
||||
function pcaValue(value) {
|
||||
if (value.includes(',')) {
|
||||
const parts = value.split(',')
|
||||
if (parts.length >= 3) {
|
||||
// 如果包含至少两个逗号,截取第三部分并去掉最后一位
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
// 如果不包含逗号或不符合条件,返回原值
|
||||
return value
|
||||
}
|
||||
/**
|
||||
* 处理多选字段
|
||||
* @param value
|
||||
*/
|
||||
function handleMultiOrDateField() {
|
||||
let finalData = deepClone(formData.value)
|
||||
//日期字段
|
||||
let dateFieldArr = rootProperties.value.filter(
|
||||
(item) => item.type === 'date' || item.type === 'datetime',
|
||||
)
|
||||
//省市区字段
|
||||
let pcaArr = rootProperties.value.filter((item) => item.type === 'pca')
|
||||
finalData = Object.keys(finalData).reduce((acc, key) => {
|
||||
let value = finalData[key]
|
||||
//省市区获取最后一位
|
||||
if (value && pcaArr.length > 0 && pcaArr.map((item) => item.key).includes(key)) {
|
||||
value = isArray(value) ? value[2] : pcaValue(value)
|
||||
}
|
||||
//是数组的就转换成字符串
|
||||
if (value && isArray(value)) {
|
||||
value = value.join(',')
|
||||
}
|
||||
//时间戳类型的日期转具体格式字符串
|
||||
if (dateFieldArr.length > 0) {
|
||||
const dateField = dateFieldArr.find((obj) => obj.key === key)
|
||||
if (dateField) {
|
||||
value =
|
||||
value && isNumber(value)
|
||||
? formatDate(
|
||||
value,
|
||||
dateField.type === 'datetime' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd',
|
||||
)
|
||||
: value
|
||||
}
|
||||
}
|
||||
acc[key] = value
|
||||
return acc
|
||||
}, {})
|
||||
return finalData
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单提交事件
|
||||
* @param e
|
||||
*/
|
||||
const beforeSubmit = async () => {
|
||||
// 判断字段必填和正则
|
||||
if (await fieldCheck(formData.value)) {
|
||||
return { status: false, data: [] }
|
||||
}
|
||||
// 处理多选字段
|
||||
let finalData = await handleMultiOrDateField()
|
||||
console.log('一对一beforeSubmit', finalData)
|
||||
return { status: true, data: [finalData] }
|
||||
}
|
||||
/**
|
||||
* 校验字段
|
||||
* @param values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldCheck = async (values: any) => {
|
||||
// 校验字段
|
||||
let flag = false
|
||||
for (const item of rootProperties.value) {
|
||||
// 校验提示
|
||||
const tip = (msg) => {
|
||||
// 提示校验未通过
|
||||
toast.warning(`${tableTxt.value}:${msg}`)
|
||||
flag = true
|
||||
}
|
||||
// 校验必填
|
||||
if (fieldRequired(item) && !values[item.key]) {
|
||||
tip(`${item.label}不能为空!`)
|
||||
break
|
||||
}
|
||||
// 校验正则
|
||||
let pattern = item?.formSchema?.pattern
|
||||
if (pattern) {
|
||||
if (pattern == 'only') {
|
||||
const res: any = await duplicateCheck({
|
||||
tableName: tableName.value,
|
||||
fieldName: item.key,
|
||||
fieldVal: values[item.key],
|
||||
dataId: formDataId.value,
|
||||
})
|
||||
if (!res.success) {
|
||||
tip(`${item.label} ${res.message}`)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const regex = new RegExp(pattern)
|
||||
if (values[item.key] && pattern && !regex.test(values[item.key])) {
|
||||
let errorInfo = item?.formSchema?.errorInfo || '格式不正确!'
|
||||
tip(`${item.label}${errorInfo}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(data) {
|
||||
console.log('一对一子表:initFormData', data)
|
||||
if (data && data.length > 0) {
|
||||
formData.value = deepClone(data[0])
|
||||
formDataId.value = formData.value?.id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建根属性
|
||||
* @param formSchema
|
||||
*/
|
||||
function createRootProperties(formSchema: any) {
|
||||
formData.value = {}
|
||||
hasFillRuleFields.value = formSchema?.hasFillRuleFields ?? []
|
||||
hasRequiredFields.value = formSchema?.required ?? []
|
||||
const properties = formSchema.properties
|
||||
let rootProps = []
|
||||
console.log('===子表one配置项 properties===', properties)
|
||||
Object.keys(properties).map((key) => {
|
||||
if (key) {
|
||||
const item = properties[key]
|
||||
formData.value[key] = ''
|
||||
let fp = FormProperty(key, item, formSchema.required)
|
||||
rootProps.push(fp)
|
||||
}
|
||||
})
|
||||
rootProps.sort((one, next) => {
|
||||
return one.formSchema.order - next.formSchema.order
|
||||
})
|
||||
rootProperties.value = [...rootProps]
|
||||
console.log('--子表one rootProperties--', rootProps)
|
||||
}
|
||||
/**
|
||||
* 获取字段类型
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
const getFieldNumberType = (item: any) => {
|
||||
return item.onlyInteger === true ? 'digit' : 'number'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认值
|
||||
*/
|
||||
function handleDefaultValue() {
|
||||
rootProperties.value.forEach((item) => {
|
||||
let field = item.key
|
||||
let { defVal, type } = item.formSchema
|
||||
loadOneFieldDefVal(defVal, type, (value) => {
|
||||
formData.value[field] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段数值
|
||||
* @param data
|
||||
*/
|
||||
const setFieldsValue = (data) => {
|
||||
console.log('一对一子表popup设置值', data)
|
||||
formData.value = { ...formData.value, ...data }
|
||||
}
|
||||
/**
|
||||
* 获取popup字段配置项
|
||||
* @param item
|
||||
*/
|
||||
const getPopupFieldConfig = (item) => {
|
||||
const { formSchema } = item
|
||||
const { destFields = '', orgFields = '' } = formSchema
|
||||
const result = orgFields.split(',').map((oField, index) => {
|
||||
return {
|
||||
source: oField,
|
||||
target: destFields.split(',')[index],
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(formSchema: any) {
|
||||
console.log('===子表one加载表单数据 schema===', formSchema)
|
||||
createRootProperties(formSchema)
|
||||
tableTxt.value = formSchema?.describe
|
||||
if (props.edit === true) {
|
||||
initFormData(props.dataInfo)
|
||||
} else {
|
||||
// 新增页面处理表单默认值
|
||||
handleDefaultValue()
|
||||
}
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
beforeSubmit,
|
||||
initFormData,
|
||||
loadTableInfo,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.online-sub-one-container {
|
||||
.form-container {
|
||||
:deep(.wd-cell-group__body) {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.onlineLoader-form {
|
||||
:deep(.wd-input__label-inner) {
|
||||
font-size: 16px;
|
||||
}
|
||||
:deep(.wd-picker__label) {
|
||||
font-size: 16px;
|
||||
}
|
||||
:deep(.wd-select-picker__label) {
|
||||
font-size: 16px;
|
||||
}
|
||||
:deep(.wd-cell__title) {
|
||||
font-size: 16px;
|
||||
}
|
||||
:deep(.wd-textarea__label-inner) {
|
||||
font-size: 16px;
|
||||
}
|
||||
:deep(.wd-input__label.is-required) {
|
||||
padding-left: 0px;
|
||||
}
|
||||
:deep(.wd-input__label.is-required::after) {
|
||||
left: -10px;
|
||||
}
|
||||
:deep(.wd-textarea__clear) {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
:deep(.wd-select-picker__clear) {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
:deep(.wd-input__clear) {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
:deep(.wd-upload__close) {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<wd-input
|
||||
v-bind="$attrs"
|
||||
:label-width="labelWidth"
|
||||
v-model="currentText"
|
||||
:disabled="disabled"
|
||||
:label="label"
|
||||
:inputMode="inputMode"
|
||||
:placeholder="placeholder"
|
||||
@input="debouncedInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { debounce } from 'lodash-es'
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
inputMode: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
required: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
}
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'update:value'])
|
||||
// 定义响应式数据;
|
||||
const currentText = ref(null)
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
currentText.value = val;
|
||||
},
|
||||
{
|
||||
immediate:true
|
||||
}
|
||||
)
|
||||
|
||||
// 更新 value 的方法
|
||||
const updateValue = () => {
|
||||
emit('update:value', currentText.value)
|
||||
}
|
||||
|
||||
// 创建防抖函数
|
||||
const debouncedInput = debounce(updateValue, 1000)
|
||||
|
||||
// 记得在组件卸载时取消防抖
|
||||
onBeforeUnmount(() => {
|
||||
debouncedInput.cancel()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout
|
||||
:navTitle="navTitle"
|
||||
type="popup"
|
||||
navRightText="确定"
|
||||
@navRight="handleConfirm"
|
||||
@navBack="handleCancel"
|
||||
>
|
||||
<view class="wrap">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
:fixed="false"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:default-page-size="15"
|
||||
>
|
||||
<!-- <template #top>-->
|
||||
<!-- <wd-search-->
|
||||
<!-- hide-cancel-->
|
||||
<!-- :placeholder="search.placeholder"-->
|
||||
<!-- v-model="search.keyword"-->
|
||||
<!-- @search="handleSearch"-->
|
||||
<!-- @clear="handleClear"-->
|
||||
<!-- />-->
|
||||
<!-- </template>-->
|
||||
<template v-if="multi">
|
||||
<wd-checkbox-group shape="square" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<view class="list" @click="hanldeCheck(index, item)">
|
||||
<view class="left text-gray-5">
|
||||
<view class="cu-avatar lg mr-4" v-if="imageField" :style="[{ backgroundImage: 'url(' + getImage(item[imageField] )+ ')' }]"></view>
|
||||
<view class="field-content">
|
||||
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||
<view class="row">
|
||||
<text class="label">{{ cItem.title }}:</text>
|
||||
<text class="value">{{ item[cItem.dataIndex] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-checkbox ref="checkboxRef" :modelValue="item.id"></wd-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</wd-checkbox-group>
|
||||
</template>
|
||||
<template v-else>
|
||||
<wd-radio-group shape="dot" v-model="checkedValue">
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<wd-cell>
|
||||
<view class="list" @click="hanldeCheck(index, item)">
|
||||
<view class="left text-gray-5">
|
||||
<view
|
||||
class="cu-avatar lg mr-4"
|
||||
v-if="imageField && item[imageField]"
|
||||
:style="[{ backgroundImage: 'url(' + item[imageField] + ')' }]"
|
||||
></view>
|
||||
<view class="field-content">
|
||||
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
|
||||
<view class="row">
|
||||
<text class="label">{{ cItem.title }}:</text>
|
||||
<text class="value">{{ item[cItem.dataIndex] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right" @click.stop>
|
||||
<wd-radio :value="item.id"></wd-radio>
|
||||
</view>
|
||||
</view>
|
||||
</wd-cell>
|
||||
</template>
|
||||
</wd-radio-group>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
|
||||
import { http } from '@/utils/http'
|
||||
import { isArray, isString } from '@/utils/is'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
|
||||
defineOptions({
|
||||
name: 'popupReportModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
selected: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
dictTable: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
dictCode: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
dictText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
imageField: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const toast = useToast()
|
||||
const show = ref(true)
|
||||
const api = {
|
||||
getColumns: '/online/cgform/api/getColumns',
|
||||
getData: '/online/cgform/api/getData',
|
||||
}
|
||||
console.log('props:::', props)
|
||||
const navTitle = ref('')
|
||||
const paging = ref(null)
|
||||
const dataList = ref([])
|
||||
// 报表id
|
||||
let rpConfigId = null
|
||||
let loadedColumns = false
|
||||
const columns = ref([])
|
||||
const selectArr = ref([])
|
||||
const checkedValue: any = ref(props.multi ? [] : '')
|
||||
const checkboxRef = ref(null)
|
||||
const search = reactive({
|
||||
keyword: '',
|
||||
placeholder: '',
|
||||
field: '',
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
|
||||
const beforeOpen = (arr) => {
|
||||
selectArr.value = arr || []
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if (checkedValue.value.length == 0) {
|
||||
toast.warning('还没选择~')
|
||||
return
|
||||
}
|
||||
const result = []
|
||||
let value = checkedValue.value
|
||||
if (!Array.isArray(checkedValue.value)) {
|
||||
value = [checkedValue.value]
|
||||
}
|
||||
value.forEach((id, index) => {
|
||||
const findIndex = dataList.value.findIndex((item) => item.id === id)
|
||||
result.push(dataList.value[findIndex])
|
||||
})
|
||||
show.value = false
|
||||
emit('change', result)
|
||||
handleClose()
|
||||
}
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
paging.value.reload()
|
||||
}
|
||||
// 图片
|
||||
function getImage(src) {
|
||||
return src?src:'';
|
||||
}
|
||||
// 清除搜索条件
|
||||
function handleClear() {
|
||||
search.keyword = ''
|
||||
handleSearch()
|
||||
}
|
||||
const hanldeCheck = (index, item) => {
|
||||
if (props.multi) {
|
||||
if (Array.isArray(checkboxRef.value)) {
|
||||
checkboxRef.value[index].toggle()
|
||||
}
|
||||
} else {
|
||||
checkedValue.value = item.id
|
||||
}
|
||||
}
|
||||
|
||||
const getRpColumns = () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (loadedColumns) {
|
||||
resolve()
|
||||
} else {
|
||||
let linkTableSelectFields = props.dictCode + ',' + props.dictText
|
||||
http
|
||||
.get(`${api.getColumns}/${props.dictTable}?linkTableSelectFields=${linkTableSelectFields}`)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
loadedColumns = true
|
||||
const { result } = res
|
||||
navTitle.value = result.description
|
||||
rpConfigId = result.code
|
||||
result.columns?.forEach((item) => {
|
||||
if (linkTableSelectFields.includes(item.dataIndex)) {
|
||||
columns.value.push(item)
|
||||
}
|
||||
})
|
||||
resolve()
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
const pararms = { pageNo, pageSize, linkTableSelectFields: '' }
|
||||
if (search.keyword) {
|
||||
pararms[search.field] = `*${search.keyword}*`
|
||||
}
|
||||
getRpColumns()
|
||||
.then(() => {
|
||||
let linkTableSelectFields = props.dictCode + ',' + props.dictText
|
||||
if (props.imageField) {
|
||||
linkTableSelectFields = linkTableSelectFields + ',' + props.imageField
|
||||
}
|
||||
pararms.linkTableSelectFields = linkTableSelectFields
|
||||
http
|
||||
.get(`${api.getData}/${props.dictTable}`, pararms)
|
||||
.then((res: any) => {
|
||||
if (res.success && res.result.records) {
|
||||
let dataRecords = res.result.records
|
||||
if (dataRecords && dataRecords.length > 0) {
|
||||
let id = dataRecords[0]['id']
|
||||
for (let item of dataRecords) {
|
||||
if (!id) {
|
||||
item.id = new Date().getTime()
|
||||
}
|
||||
if (props.imageField && item[props.imageField]) {
|
||||
let imgUrlArr = item[props.imageField].split(',')
|
||||
item[props.imageField] =
|
||||
imgUrlArr.length > 0 ? getFileAccessHttpUrl(imgUrlArr[0]) : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO
|
||||
if (selectArr.value && isArray(selectArr) && selectArr.length > 0) {
|
||||
//checkedValue.value = [...selectArr]
|
||||
}
|
||||
paging.value.complete(dataRecords ?? [])
|
||||
} else {
|
||||
paging.value.complete(false)
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
const init = () => {
|
||||
if (props.selected.length) {
|
||||
if (props.multi) {
|
||||
if (isArray(props.selected)) {
|
||||
checkedValue.value = props.selected
|
||||
} else if (isString(props.selected)) {
|
||||
checkedValue.value = props.selected.split(',')
|
||||
}
|
||||
} else {
|
||||
if (isString(props.selected)) {
|
||||
checkedValue.value = props.selected
|
||||
} else if (isArray(props.selected)) {
|
||||
// @ts-ignore
|
||||
checkedValue.value = props.selected.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
init()
|
||||
defineExpose({
|
||||
beforeOpen,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrap {
|
||||
height: 100%;
|
||||
.mr-4 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
:deep(.wd-cell) {
|
||||
--wot-color-white: tranparent;
|
||||
--wot-cell-padding: 0;
|
||||
.wd-cell__wrapper {
|
||||
--wot-cell-wrapper-padding: 0;
|
||||
}
|
||||
.wd-cell__left {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.wd-checkbox-group) {
|
||||
--wot-checkbox-bg: tranparent;
|
||||
}
|
||||
:deep(.wd-radio-group) {
|
||||
--wot-radio-bg: tranparent;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.field-content {
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
:deep(.wd-checkbox) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
v-model="selected"
|
||||
filterable
|
||||
clearable
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { isArray, isString } from 'lodash';
|
||||
import {http} from "@/utils/http"; // 假设使用 lodash 来判断类型
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
dict: {
|
||||
type: [Array, String],
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
dictStr: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref([]);
|
||||
const options = ref([]);
|
||||
|
||||
// 初始化选项
|
||||
const initSelections = async () => {
|
||||
options.value = []
|
||||
if (props.type === 'sel_search' && props.dictStr) {
|
||||
let temp = props.dictStr
|
||||
if (temp.indexOf(' ') > 0) {
|
||||
temp = encodeURI(props.dictStr)
|
||||
}
|
||||
try {
|
||||
const res = await http.get('/sys/dict/getDictItems/' + temp)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!props.dict || props.dict.length === 0) {
|
||||
return
|
||||
}
|
||||
if (isString(props.dict)) {
|
||||
try {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
let valueStr='';
|
||||
if (value && isArray(value)) {
|
||||
valueStr = value.join(',')
|
||||
}
|
||||
emit('update:modelValue', valueStr);
|
||||
emit('change', valueStr);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听value 的变化
|
||||
watchEffect(()=>{
|
||||
props.modelValue && initVal()
|
||||
})
|
||||
//初始化数值
|
||||
function initVal(){
|
||||
selected.value = isString(props.modelValue)? props.modelValue.split(',') :[];
|
||||
}
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape){
|
||||
border-radius:0 !important;
|
||||
}
|
||||
</style>
|
||||
178
JeecgUniapp-master/src/components/online/view/online-date.vue
Normal file
178
JeecgUniapp-master/src/components/online/view/online-date.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<view class="pickerArea" :class="{ clear: !!currentTime }">
|
||||
<!-- 周视图 -->
|
||||
<wd-calendar
|
||||
v-if="type == 'week'"
|
||||
v-model="currentTime"
|
||||
type="week"
|
||||
:disabled="disabled"
|
||||
:default-value="defaultValue"
|
||||
:labelWidth="labelWidth"
|
||||
:label="label"
|
||||
@confirm="handleConfirm" />
|
||||
<!-- 季度视图 -->
|
||||
<wd-picker
|
||||
v-else-if="type == 'quarter'"
|
||||
v-model="currentQuarter"
|
||||
:disabled="disabled"
|
||||
:labelWidth="labelWidth"
|
||||
:label="label"
|
||||
:columns="quarterColumns"
|
||||
@open="quarterOpen"
|
||||
@confirm="quarterConfirm"/>
|
||||
<!-- 其他视图 -->
|
||||
<wd-datetime-picker
|
||||
v-else
|
||||
:disabled="disabled"
|
||||
:minDate="-662716800000"
|
||||
:maxDate="4102416000000"
|
||||
:default-value="defaultValue"
|
||||
:type="type"
|
||||
:labelWidth="labelWidth"
|
||||
v-model="currentTime"
|
||||
:label="label"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
<view v-if="!!currentTime && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义 props
|
||||
import { isString } from '@/utils/is'
|
||||
import dayjs from 'dayjs'
|
||||
import {dateToQuarterStart} from "@/common/uitls";
|
||||
|
||||
defineOptions({
|
||||
name: 'online-date',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'date',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Date],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:value'])
|
||||
// 定义响应式数据;
|
||||
const currentQuarter = ref('');
|
||||
// 定义响应式数据;
|
||||
const currentTime = ref(props.type == 'week' ? null : '');
|
||||
// 默認
|
||||
const defaultValue = ref(Date.now())
|
||||
// 季度选择器列
|
||||
const quarterColumns = ref([])
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.type == 'quarter' && quarterColumns.value.length == 0) {
|
||||
generateQuarterOptions()
|
||||
}
|
||||
initVal(val)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 初始化值
|
||||
function initVal(val){
|
||||
if (props.type == 'quarter') {
|
||||
currentQuarter.value = dateToQuarterStart(val);
|
||||
}else{
|
||||
if (typeof val == 'object') {
|
||||
currentTime.value = dayjs(val).valueOf();
|
||||
} else {
|
||||
currentTime.value = isString(val) ? new Date(val).getTime() : val;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const handleConfirm = (e) => {
|
||||
emit('update:value', currentTime.value)
|
||||
emit('change', currentTime.value)
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const quarterOpen = () => {
|
||||
!quarterColumns.value.length && generateQuarterOptions();
|
||||
if(!currentQuarter.value) {
|
||||
currentQuarter.value = dateToQuarterStart(new Date());
|
||||
}
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const quarterConfirm = (e) => {
|
||||
emit('update:value', currentQuarter.value)
|
||||
emit('change', currentQuarter.value)
|
||||
}
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
currentTime.value = null
|
||||
handleConfirm(null)
|
||||
}
|
||||
// 生成季度选择器列
|
||||
function generateQuarterOptions(){
|
||||
const options = [];
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startYear = currentYear - 40;
|
||||
const endYear = currentYear + 40;
|
||||
const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => endYear - i).reverse();
|
||||
years.forEach(year => {
|
||||
options.push({label: `${year}年 Q1`, value: `${year}-01-01`})
|
||||
options.push({label: `${year}年 Q2`, value: `${year}-04-01`})
|
||||
options.push({label: `${year}年 Q3`, value: `${year}-07-01`})
|
||||
options.push({label: `${year}年 Q4`, value: `${year}-10-01`})
|
||||
})
|
||||
quarterColumns.value = [...options];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(10px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-picker__arrow) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view>
|
||||
<template v-if="fileList && fileList.length > 0">
|
||||
<view class="cu-list menu-avatar ol-file-list">
|
||||
<view
|
||||
class="cu-item"
|
||||
:class="modalName == 'move-box-' + index ? 'move-cur' : ''"
|
||||
v-for="(item, index) in fileList"
|
||||
:key="index"
|
||||
@touchstart="ListTouchStart"
|
||||
@touchmove="ListTouchMove"
|
||||
@touchend="ListTouchEnd"
|
||||
:data-target="'move-box-' + index"
|
||||
>
|
||||
<view class="flex align-center">
|
||||
<text
|
||||
class="mr-4px text-blue"
|
||||
style="font-size: 18px"
|
||||
:class="fileType(item.name) ? 'cuIcon-text' : 'cuIcon-pic'"
|
||||
></text>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view class="move">
|
||||
<view @tap="DelFile" class="bg-red" :data-index="index">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view class="cu-bar bg-white">
|
||||
<wd-button @tap="ChooseFile" size="small" :disabled="disabled" plain type="primary">
|
||||
<text class="cuIcon-upload"></text>
|
||||
上传
|
||||
</wd-button>
|
||||
</view>
|
||||
<LFile ref="customFile" @up-success="onSuccess"></LFile>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import LFile from '@/components/LFile/LFile.vue';
|
||||
import { useUserStore } from '@/store';
|
||||
import { getEnvBaseUrl } from '@/utils';
|
||||
import { useToast } from 'wot-design-uni';
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls';
|
||||
//图片类型
|
||||
const imgTypeArr = ['png', 'jpg', 'jpeg', 'bmp']
|
||||
const userStore = useUserStore()
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
// 事件
|
||||
const emits = defineEmits(['change', 'input', 'update:value'])
|
||||
// 提示
|
||||
const toast = useToast()
|
||||
// 组件
|
||||
const customFile = ref(null)
|
||||
// 文件数据
|
||||
const fileList = ref([])
|
||||
// token
|
||||
const token = ref(userStore.userInfo.token)
|
||||
const modalName = ref(null)
|
||||
const listTouchStart = ref(0)
|
||||
const listTouchDirection = ref(null)
|
||||
|
||||
// ListTouch触摸开始
|
||||
const ListTouchStart = (e) => {
|
||||
listTouchStart.value = e.touches[0].pageX
|
||||
}
|
||||
|
||||
// ListTouch计算方向
|
||||
const ListTouchMove = (e) => {
|
||||
listTouchDirection.value = e.touches[0].pageX - listTouchStart.value > 0 ? 'right' : 'left'
|
||||
}
|
||||
|
||||
// ListTouch计算滚动
|
||||
const ListTouchEnd = (e) => {
|
||||
if (listTouchDirection.value == 'left') {
|
||||
modalName.value = e.currentTarget.dataset.target
|
||||
} else {
|
||||
modalName.value = null
|
||||
}
|
||||
listTouchDirection.value = null
|
||||
}
|
||||
/**
|
||||
* 选择文件
|
||||
* @constructor
|
||||
*/
|
||||
const ChooseFile = () => {
|
||||
if (props.maxNum && fileList.value.length >= props.maxNum) {
|
||||
toast.warning('最多只能上传' + props.maxNum + '个文件')
|
||||
return
|
||||
}
|
||||
customFile.value.upload({
|
||||
// #ifdef APP-PLUS
|
||||
currentWebview: getCurrentPages()[getCurrentPages().length - 1].$getAppWebview(),
|
||||
// #endif
|
||||
header: {
|
||||
'X-Access-Token': token.value,
|
||||
},
|
||||
url: `${getEnvBaseUrl()}/sys/common/upload`,
|
||||
})
|
||||
}
|
||||
|
||||
const onSuccess = (res) => {
|
||||
console.log('这是上传成功返回数值res:', res)
|
||||
let fileObj = res.data
|
||||
// #ifdef APP-PLUS
|
||||
fileObj = JSON.parse(res.data.id)
|
||||
// #endif
|
||||
if (fileObj.success) {
|
||||
let file = {
|
||||
name: res.fileName,
|
||||
path: fileObj.message,
|
||||
url: getFileAccessHttpUrl(fileObj.message),
|
||||
}
|
||||
fileList.value.unshift(file)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
}
|
||||
// 获取文件路径
|
||||
const pathArr = (arg) => {
|
||||
return fileList.value.map((item) => item[arg])
|
||||
}
|
||||
// 改变表单值
|
||||
const changeOnlineFormValue = () => {
|
||||
let arr = pathArr('path')
|
||||
let str = arr.join(',')
|
||||
emits('change', str)
|
||||
emits('input', str)
|
||||
emits('update:value', str)
|
||||
}
|
||||
|
||||
const DelFile = (e) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
fileList.value.splice(e.currentTarget.dataset.index, 1)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const loadFile = () => {
|
||||
if (!props.value || props.value.length == 0) {
|
||||
return
|
||||
}
|
||||
let pathArr = props.value.split(',')
|
||||
let fileArray = []
|
||||
pathArr.map((path) => {
|
||||
let seg = path.lastIndexOf('/')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
fileArray.push({
|
||||
name: formatPath(seg == 0 ? path : path.substr(seg + 1)),
|
||||
path: path,
|
||||
url: getFileAccessHttpUrl(path),
|
||||
})
|
||||
})
|
||||
fileList.value = [...fileArray]
|
||||
}
|
||||
|
||||
const formatPath = (path) => {
|
||||
let seg = path.lastIndexOf('.')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
let pathSuffix = path.substr(seg)
|
||||
if (path.length > 20) {
|
||||
return path.substring(0, 15) + '(..)' + pathSuffix
|
||||
} else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
const fileType = (name) => {
|
||||
let seg = name.lastIndexOf('.')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
let pathSuffix = name.substr(seg + 1)
|
||||
return imgTypeArr.indexOf(pathSuffix) == -1
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadFile()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ol-file-block {
|
||||
display: block;
|
||||
}
|
||||
.ol-file-block .action {
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.ol-file-list > .cu-item {
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 45px;
|
||||
}
|
||||
</style>
|
||||
171
JeecgUniapp-master/src/components/online/view/online-file.vue
Normal file
171
JeecgUniapp-master/src/components/online/view/online-file.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<wd-upload
|
||||
v-model:file-list="fileList"
|
||||
accept="all"
|
||||
:upload-method="customUpload"
|
||||
:disabled="disabled"
|
||||
:limit="maxNum"
|
||||
:before-remove="delFile"
|
||||
:multiple="maxNum && maxNum > 1"
|
||||
></wd-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
||||
import { getEnvBaseUploadUrl,getEnvBaseUrl } from '@/utils'
|
||||
import { useUserStore } from '@/store'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import { isString } from '@/utils/is'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
const toast = useToast()
|
||||
const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/sys/common/upload`
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
uploadFileType: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['change', 'update:value'])
|
||||
// 定义响应式数据
|
||||
const fileList = ref([])
|
||||
/**
|
||||
* 自定义上传方法
|
||||
* @param file
|
||||
* @param formData
|
||||
* @param options
|
||||
*/
|
||||
const customUpload: UploadMethod = (file, formData, options) => {
|
||||
const userStore = useUserStore()
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: VITE_UPLOAD_BASEURL,
|
||||
header: {
|
||||
'X-Access-Token': userStore.userInfo.token,
|
||||
'X-Tenant-Id': userStore.userInfo.tenantId,
|
||||
...options.header,
|
||||
},
|
||||
name: options.name,
|
||||
fileName: options.name,
|
||||
fileType: options.fileType,
|
||||
formData,
|
||||
filePath: file.url,
|
||||
success(res: any) {
|
||||
if (res.statusCode === options.statusCode) {
|
||||
let data = res.data
|
||||
if (data && isString(data)) {
|
||||
data = JSON.parse(data)
|
||||
}
|
||||
// 设置上传成功
|
||||
if (data && data.success) {
|
||||
const file = {
|
||||
id: new Date().getTime(),
|
||||
name: options.name,
|
||||
path: data.message,
|
||||
url: getFileAccessHttpUrl(data.message),
|
||||
}
|
||||
fileList.value.unshift(file)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
} else {
|
||||
// 设置上传失败
|
||||
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.info('upload fail', err)
|
||||
// 设置上传失败
|
||||
options.onError(err, file, formData)
|
||||
},
|
||||
})
|
||||
// 设置当前文件加载的百分比
|
||||
uploadTask.onProgressUpdate((res) => {
|
||||
options.onProgress(res, file)
|
||||
})
|
||||
}
|
||||
|
||||
const changeOnlineFormValue = () => {
|
||||
console.log('changeOnlineFormValue fileList.value', fileList)
|
||||
const arr = fileList.value.map((item) => item['path'])
|
||||
const str = arr.join(',')
|
||||
emit('change', str)
|
||||
emit('update:value', str)
|
||||
}
|
||||
|
||||
const delFile = ({ file, fileList, resolve }) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
console.log('当前删除文件', file)
|
||||
changeOnlineFormValue()
|
||||
toast.success('删除成功')
|
||||
resolve(true)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
resolve(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
const loadFile = () => {
|
||||
if (!props.value || props.value.length === 0) {
|
||||
return
|
||||
}
|
||||
const pathArr = props.value.split(',')
|
||||
const fileArray = []
|
||||
pathArr.forEach((path) => {
|
||||
const seg = path.lastIndexOf('/')
|
||||
fileArray.push({
|
||||
name: path.substr(seg < 0 ? 0 : seg),
|
||||
path: path,
|
||||
url: getFileAccessHttpUrl(path),
|
||||
})
|
||||
})
|
||||
console.log('当前图片回显数据', fileArray)
|
||||
fileList.value = [...fileArray]
|
||||
}
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadFile()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时加载文件
|
||||
onMounted(() => {
|
||||
loadFile()
|
||||
})
|
||||
</script>
|
||||
171
JeecgUniapp-master/src/components/online/view/online-image.vue
Normal file
171
JeecgUniapp-master/src/components/online/view/online-image.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<wd-upload
|
||||
v-model:file-list="fileList"
|
||||
accept="image"
|
||||
:upload-method="customUpload"
|
||||
:disabled="disabled"
|
||||
:limit="maxNum"
|
||||
:before-remove="delFile"
|
||||
:multiple="maxNum && maxNum > 1"
|
||||
></wd-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
||||
import { getEnvBaseUploadUrl, getEnvBaseUrl } from '@/utils'
|
||||
import { useUserStore } from '@/store'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import { isString } from '@/utils/is'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
const toast = useToast()
|
||||
const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/sys/common/upload`
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
uploadFileType: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['change', 'update:value'])
|
||||
// 定义响应式数据
|
||||
const fileList = ref([])
|
||||
/**
|
||||
* 自定义上传方法
|
||||
* @param file
|
||||
* @param formData
|
||||
* @param options
|
||||
*/
|
||||
const customUpload: UploadMethod = (file, formData, options) => {
|
||||
const userStore = useUserStore()
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: VITE_UPLOAD_BASEURL,
|
||||
header: {
|
||||
'X-Access-Token': userStore.userInfo.token,
|
||||
'X-Tenant-Id': userStore.userInfo.tenantId,
|
||||
...options.header,
|
||||
},
|
||||
name: options.name,
|
||||
fileName: options.name,
|
||||
fileType: options.fileType,
|
||||
formData,
|
||||
filePath: file.url,
|
||||
success(res: any) {
|
||||
if (res.statusCode === options.statusCode) {
|
||||
let data = res.data
|
||||
if (data && isString(data)) {
|
||||
data = JSON.parse(data)
|
||||
}
|
||||
// 设置上传成功
|
||||
if (data && data.success) {
|
||||
const file = {
|
||||
id: new Date().getTime(),
|
||||
name: options.name,
|
||||
path: data.message,
|
||||
url: getFileAccessHttpUrl(data.message),
|
||||
}
|
||||
fileList.value.unshift(file)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
} else {
|
||||
// 设置上传失败
|
||||
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.info('upload fail', err)
|
||||
// 设置上传失败
|
||||
options.onError(err, file, formData)
|
||||
},
|
||||
})
|
||||
// 设置当前文件加载的百分比
|
||||
uploadTask.onProgressUpdate((res) => {
|
||||
options.onProgress(res, file)
|
||||
})
|
||||
}
|
||||
|
||||
const changeOnlineFormValue = () => {
|
||||
console.log('changeOnlineFormValue fileList.value', fileList)
|
||||
const arr = fileList.value.map((item) => item['path'])
|
||||
const str = arr.join(',')
|
||||
emit('change', str)
|
||||
emit('update:value', str)
|
||||
}
|
||||
|
||||
const delFile = ({ file, fileList, resolve }) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
console.log('当前删除文件', file)
|
||||
changeOnlineFormValue()
|
||||
toast.success('删除成功')
|
||||
resolve(true)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
resolve(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
const loadFile = () => {
|
||||
if (!props.value || props.value.length === 0) {
|
||||
return
|
||||
}
|
||||
const pathArr = props.value.split(',')
|
||||
const fileArray = []
|
||||
pathArr.forEach((path) => {
|
||||
const seg = path.lastIndexOf('/')
|
||||
fileArray.push({
|
||||
name: path.substr(seg < 0 ? 0 : seg),
|
||||
path: path,
|
||||
url: getFileAccessHttpUrl(path),
|
||||
})
|
||||
})
|
||||
console.log('当前图片回显数据', fileArray)
|
||||
fileList.value = [...fileArray]
|
||||
}
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadFile()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时加载文件
|
||||
onMounted(() => {
|
||||
loadFile()
|
||||
})
|
||||
</script>
|
||||
150
JeecgUniapp-master/src/components/online/view/online-multi.vue
Normal file
150
JeecgUniapp-master/src/components/online/view/online-multi.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
v-model="selected"
|
||||
filterable
|
||||
clearable
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { isArray, isString } from 'lodash'
|
||||
import {http} from "@/utils/http";
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
dict: {
|
||||
type: [Array, String],
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
dictStr: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits([ 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref([]);
|
||||
const options = ref([]);
|
||||
|
||||
// 初始化选项
|
||||
const initSelections = async () => {
|
||||
options.value = []
|
||||
if (props.type === 'sel_search' && props.dictStr) {
|
||||
let temp = props.dictStr
|
||||
if (temp.indexOf(' ') > 0) {
|
||||
temp = encodeURI(props.dictStr)
|
||||
}
|
||||
try {
|
||||
const res = await http.get('/sys/dict/getDictItems/' + temp)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!props.dict || props.dict.length === 0) {
|
||||
return
|
||||
}
|
||||
if (isString(props.dict)) {
|
||||
try {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
console.log("options.value ",options.value )
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
console.log("下拉多选handleChange",value);
|
||||
let valueStr = "";
|
||||
if (value && isArray(value)) {
|
||||
valueStr = value.join(',');
|
||||
}
|
||||
emit('update:modelValue', valueStr);
|
||||
emit('change', valueStr);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if(props?.modelValue){
|
||||
selected.value = isString(props.modelValue)? props.modelValue.split(','):[];
|
||||
}else{
|
||||
selected.value = []
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape){
|
||||
border-radius:0 !important;
|
||||
}
|
||||
</style>
|
||||
108
JeecgUniapp-master/src/components/online/view/online-pca.vue
Normal file
108
JeecgUniapp-master/src/components/online/view/online-pca.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view>
|
||||
<wd-picker
|
||||
:columns="columns"
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
v-model="selected"
|
||||
:column-change="onChangeDistrict"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getAreaArrByCode,getPcaOptionData } from '@/common/areaData/Area'
|
||||
import {isString} from "@/utils/is";
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '130px',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String,Array],
|
||||
required: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '请选择省市区',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
backAll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['input', 'change', 'update:value'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref([])
|
||||
const district = { ...getPcaOptionData() }
|
||||
const columns = ref([
|
||||
district[0],
|
||||
district[district[0][0].value],
|
||||
district[district[district[0][0].value][0].value]
|
||||
])
|
||||
const onChangeDistrict = (pickerView, value, columnIndex, resolve) => {
|
||||
const item = value[columnIndex]
|
||||
if (columnIndex === 0) {
|
||||
pickerView.setColumnData(1, district[item.value])
|
||||
pickerView.setColumnData(2, district[district[item.value][0].value])
|
||||
} else if (columnIndex === 1) {
|
||||
pickerView.setColumnData(2, district[item.value])
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
|
||||
const handleConfirm = ({value}) => {
|
||||
if(value){
|
||||
emits('update:value', props.backAll?value:value[2]);
|
||||
}
|
||||
}
|
||||
// 监听 value 变化
|
||||
watch(
|
||||
() => props.value,
|
||||
async (val) => {
|
||||
if(props.value && isString(props.value)){
|
||||
let arr = getAreaArrByCode(props.value);
|
||||
selected.value = arr;
|
||||
await initColumnData(arr);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
/**
|
||||
* 初始化列数据
|
||||
* @param val
|
||||
*/
|
||||
function initColumnData(val){
|
||||
if(val && val.length){
|
||||
let first = district[0];
|
||||
let second = district[selected.value[0]];
|
||||
let third = district[selected.value[1]];
|
||||
columns.value = [first, second, third]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="Popup">
|
||||
<view @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
type="text"
|
||||
readonly
|
||||
v-model="showText"
|
||||
clearable
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</view>
|
||||
<LinkRecordsModal
|
||||
v-if="reportModal.show"
|
||||
:selected="value"
|
||||
ref="lrmRef"
|
||||
:dictCode="dictCode"
|
||||
:dictTable="dictTable"
|
||||
:dictText="dictText"
|
||||
:multi="multi"
|
||||
:imageField="imageField"
|
||||
@close="handleClose"
|
||||
@change="handleChange"
|
||||
></LinkRecordsModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, useAttrs } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import LinkRecordsModal from './link-records-modal.vue'
|
||||
import {http} from "@/utils/http";
|
||||
defineOptions({
|
||||
name: 'onlinePopupLinkRecord',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
formSchema: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:value','selected'])
|
||||
|
||||
const toast = useToast()
|
||||
const lrmRef = ref()
|
||||
const showText = ref('')
|
||||
const selectVal = ref([])
|
||||
const attrs: any = useAttrs()
|
||||
const reportModal = reactive({
|
||||
show: false,
|
||||
})
|
||||
//字典code
|
||||
const dictCode = computed(() => props.formSchema?.dictCode)
|
||||
//字典table
|
||||
const dictTable = computed(() => props.formSchema?.dictTable)
|
||||
//字典文本
|
||||
const dictText = computed(() => props.formSchema?.dictText)
|
||||
//是否多选
|
||||
const multi = computed(() => {
|
||||
if(props.formSchema?.fieldExtendJson){
|
||||
const extendJson = JSON.parse(props.formSchema.fieldExtendJson)
|
||||
return extendJson?.multiSelect
|
||||
}
|
||||
return false
|
||||
})
|
||||
//图片字段
|
||||
const imageField = computed(() => {
|
||||
if(props.formSchema?.fieldExtendJson){
|
||||
const extendJson = JSON.parse(props.formSchema.fieldExtendJson)
|
||||
return extendJson?.imageField
|
||||
}
|
||||
return ''
|
||||
})
|
||||
//首次加载
|
||||
const firstLoad = ref(true);
|
||||
/**
|
||||
* 监听value数值
|
||||
*/
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
val && loadValue()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
//加载数据
|
||||
function loadValue(){
|
||||
console.log('关联记录loadValue',firstLoad.value)
|
||||
if(!firstLoad.value){
|
||||
return
|
||||
}
|
||||
let linkTableSelectFields = dictCode.value + ',' + dictText.value;
|
||||
let superQueryParams = [{"field":"id","rule":"in","val": props.value}];
|
||||
let param = {
|
||||
linkTableSelectFields,
|
||||
superQueryMatchType:"and",
|
||||
superQueryParams: encodeURI(JSON.stringify(superQueryParams))
|
||||
};
|
||||
let titleField = props.formSchema?.dictText && props.formSchema?.dictText.split(",")[0];
|
||||
http.get(`/online/cgform/api/getData/${dictTable.value}`,param).then((res:any)=>{
|
||||
if(res.success){
|
||||
let selectedList = res.result.records || [];
|
||||
let labels = [];
|
||||
let values = [];
|
||||
selectedList.forEach(item=>{
|
||||
if(item.id){
|
||||
values.push(item.id);
|
||||
labels.push(item[titleField]);
|
||||
}
|
||||
})
|
||||
showText.value = labels.join(',');
|
||||
selectVal.value = values;
|
||||
emit('selected', selectedList,props.name);
|
||||
}
|
||||
})
|
||||
firstLoad.value = false;
|
||||
}
|
||||
|
||||
//回显数值
|
||||
function callBack(rows) {
|
||||
//匹配popup设置的回调值
|
||||
let values = []
|
||||
let labels = []
|
||||
let titleField = props.formSchema?.dictText && props.formSchema?.dictText.split(",")[0];
|
||||
rows.forEach(item=>{
|
||||
if(item.id){
|
||||
values.push(item.id);
|
||||
labels.push(item[titleField]);
|
||||
}
|
||||
})
|
||||
showText.value = labels.join(',')
|
||||
selectVal.value = values
|
||||
emit('selected', rows,props.name)
|
||||
emit('change', values.join(','))
|
||||
emit('update:value', values.join(','))
|
||||
}
|
||||
//点击事件
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
reportModal.show = true
|
||||
//lrmRef.value.beforeOpen(selectVal.value)
|
||||
}
|
||||
}
|
||||
//关闭事件
|
||||
const handleClose = () => {
|
||||
reportModal.show = false
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
console.log('选中的值:', data)
|
||||
callBack(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
157
JeecgUniapp-master/src/components/online/view/online-radio.vue
Normal file
157
JeecgUniapp-master/src/components/online/view/online-radio.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<wd-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
clearable
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@confirm="handleChange"
|
||||
></wd-picker>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:show-confirm="false"
|
||||
:label="label"
|
||||
v-model="selected"
|
||||
type="radio"
|
||||
filterable
|
||||
clearable
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { isArray, isString } from 'lodash'
|
||||
import { http } from "@/utils/http";
|
||||
import { isNullOrUnDef } from "@/utils/is"; // 假设使用 lodash 来判断类型
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
dict: {
|
||||
type: [Array, String],
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
dictStr: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref('');
|
||||
const options = ref([]);
|
||||
|
||||
// 初始化选项
|
||||
const initSelections = async () => {
|
||||
options.value = []
|
||||
if (props.type === 'sel_search' && props.dictStr) {
|
||||
let temp = props.dictStr
|
||||
if (temp.indexOf(' ') > 0) {
|
||||
temp = encodeURI(props.dictStr)
|
||||
}
|
||||
try {
|
||||
const res = await http.get('/sys/dict/getDictItems/' + temp)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!props.dict || props.dict.length === 0) {
|
||||
return
|
||||
}
|
||||
if (isString(props.dict)) {
|
||||
try {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
selected.value = isNullOrUnDef(val)?'':val;
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
163
JeecgUniapp-master/src/components/online/view/online-select.vue
Normal file
163
JeecgUniapp-master/src/components/online/view/online-select.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<!-- 带搜索 -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
type="radio"
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
:show-confirm="false"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 常规下拉 -->
|
||||
<wd-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@confirm="handleChange"
|
||||
></wd-picker>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { isArray, isString } from 'lodash'
|
||||
import { http } from '@/utils/http'
|
||||
import { isNullOrUnDef } from '@/utils/is'
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
dict: {
|
||||
type: [Array, String],
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
dictStr: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref<any>('')
|
||||
const options = ref<any>([])
|
||||
|
||||
// 初始化选项
|
||||
const initSelections = async () => {
|
||||
options.value = []
|
||||
if (props.type === 'sel_search' && props.dictStr) {
|
||||
let temp = props.dictStr
|
||||
if (temp.indexOf(' ') > 0) {
|
||||
temp = encodeURI(props.dictStr)
|
||||
}
|
||||
try {
|
||||
const res: any = await http.get('/sys/dict/getDictItems/' + temp)
|
||||
if (res.success) {
|
||||
options.value = (res.result ?? []).filter((item) => item !== null)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
if (!props.dict || props.dict.length === 0) {
|
||||
return
|
||||
}
|
||||
if (isString(props.dict)) {
|
||||
try {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res: any = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(
|
||||
() => props.dict,
|
||||
() => {
|
||||
initSelections()
|
||||
},
|
||||
)
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
selected.value = isNullOrUnDef(props.modelValue) ? '' : val
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<wd-datetime-picker :disabled="disabled" type="time" :labelWidth="labelWidth" v-model="currentTime" :label="label" @confirm="handleConfirm" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义 props
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number,Date],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:value'])
|
||||
// 定义响应式数据;
|
||||
const currentTime = ref('')
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
currentTime.value = initData(val);
|
||||
},
|
||||
{
|
||||
immediate:true
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param val
|
||||
* @returns {*|string}
|
||||
*/
|
||||
function initData(time){
|
||||
try {
|
||||
if(typeof time == 'object' ){
|
||||
time = dayjs(time).format('HH:mm:ss');
|
||||
}
|
||||
}catch (e) {
|
||||
console.log("initData>>e",e);
|
||||
}
|
||||
return time?time:'';
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const handleConfirm = (e) => {
|
||||
emit('update:value', currentTime.value+':00')
|
||||
emit('change', currentTime.value+':00')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
109
JeecgUniapp-master/src/components/registerGlobComp.ts
Normal file
109
JeecgUniapp-master/src/components/registerGlobComp.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { App } from 'vue';
|
||||
import StatusTip from '@/pages-work/components/statusTip.vue';
|
||||
import JBar from '@/pages-work/components/echarts/JBar/index.vue';
|
||||
import JBackgroundBar from '@/pages-work/components/echarts/JBackgroundBar/index.vue';
|
||||
import JDynamicBar from '@/pages-work/components/echarts/JDynamicBar/index.vue';
|
||||
import JStackBar from '@/pages-work/components/echarts/JStackBar/index.vue';
|
||||
import JMultipleBar from '@/pages-work/components/echarts/JMultipleBar/index.vue';
|
||||
import JNegativeBar from '@/pages-work/components/echarts/JNegativeBar/index.vue';
|
||||
import JMixLineBar from '@/pages-work/components/echarts/JMixLineBar/index.vue';
|
||||
import JProgress from '@/pages-work/components/echarts/JProgress/index.vue';
|
||||
import JLine from '@/pages-work/components/echarts/JLine/index.vue';
|
||||
import JMultipleLine from '@/pages-work/components/echarts/JMultipleLine/index.vue';
|
||||
import JSmoothLine from '@/pages-work/components/echarts/JSmoothLine/index.vue';
|
||||
import JStepLine from '@/pages-work/components/echarts/JStepLine/index.vue';
|
||||
import JPie from '@/pages-work/components/echarts/JPie/index.vue';
|
||||
import JRing from '@/pages-work/components/echarts/JRing/index.vue';
|
||||
import JFunnel from '@/pages-work/components/echarts/JFunnel/index.vue';
|
||||
import JPyramidFunnel from '@/pages-work/components/echarts/JPyramidFunnel/index.vue';
|
||||
import JRadar from '@/pages-work/components/echarts/JRadar/index.vue';
|
||||
import JCircleRadar from '@/pages-work/components/echarts/JCircleRadar/index.vue';
|
||||
import JGauge from '@/pages-work/components/echarts/JGauge/index.vue';
|
||||
import JColorGauge from '@/pages-work/components/echarts/JColorGauge/index.vue';
|
||||
import JScatter from '@/pages-work/components/echarts/JScatter/index.vue';
|
||||
import JBubble from '@/pages-work/components/echarts/JBubble/index.vue';
|
||||
import DoubleLineBar from '@/pages-work/components/echarts/DoubleLineBar/index.vue';
|
||||
import JRose from '@/pages-work/components/echarts/JRose/index.vue';
|
||||
import JHorizontalBar from '@/pages-work/components/echarts/JHorizontalBar/index.vue';
|
||||
import JArea from '@/pages-work/components/echarts/JArea/index.vue';
|
||||
import JPictorial from '@/pages-work/components/echarts/JPictorial/index.vue';
|
||||
import JPictorialBar from '@/pages-work/components/echarts/JPictorialBar/index.vue';
|
||||
import JAreaMap from '@/pages-work/components/echarts/map/JAreaMap/index.vue';
|
||||
import JBubbleMap from '@/pages-work/components/echarts/map/JBubbleMap/index.vue';
|
||||
import JBarMap from '@/pages-work/components/echarts/map/JBarMap/index.vue';
|
||||
import JHeatMap from '@/pages-work/components/echarts/map/JHeatMap/index.vue';
|
||||
import JFlyLineMap from '@/pages-work/components/echarts/map/JFlyLineMap/index.vue';
|
||||
import JTotalFlyLineMap from '@/pages-work/components/echarts/map/TotalFlyLineMap/index.vue';
|
||||
import JTotalBarMap from '@/pages-work/components/echarts/map/TotalBarMap/index.vue';
|
||||
|
||||
//非echart组件
|
||||
import JCarousel from '@/pages-work/components/drag/carousel/index.vue';
|
||||
import JIframe from '@/pages-work/components/drag/iframe/index.vue';
|
||||
import JDragEditor from '@/pages-work/components/drag/editor/index.vue';
|
||||
import JImg from '@/pages-work/components/drag/img/index.vue';
|
||||
import JNumber from '@/pages-work/components/drag/number/index.vue';
|
||||
import JText from '@/pages-work/components/drag/text/index.vue';
|
||||
import JCalendar from '@/pages-work/components/drag/calendar/index.vue';
|
||||
import JCurrentTime from '@/pages-work/components/drag/currentTime/time.vue';
|
||||
import JList from '@/pages-work/components/drag/list/index.vue';
|
||||
import JRadioButton from '@/pages-work/components/drag/radiobutton/index.vue';
|
||||
import JCommonTable from '@/pages-work/components/drag/table/index.vue';
|
||||
import JQuickNav from '@/pages-work/components/drag/JQuickNav/index.vue';
|
||||
import JForm from '@/pages-work/components/drag/form/index.vue';
|
||||
|
||||
|
||||
// 全局注册组件
|
||||
export function registerGlobComp(app: App) {
|
||||
app.component('statusTip', StatusTip)
|
||||
app.component('JBar', JBar)
|
||||
app.component('JMultipleBar', JMultipleBar)
|
||||
app.component('JNegativeBar', JNegativeBar)
|
||||
app.component('JLine', JLine)
|
||||
app.component('JMultipleLine', JMultipleLine)
|
||||
app.component('JPie', JPie)
|
||||
app.component('JRing', JRing)
|
||||
app.component('JFunnel', JFunnel)
|
||||
app.component('JPyramidFunnel', JPyramidFunnel)
|
||||
app.component('JRadar', JRadar)
|
||||
app.component('JCircleRadar', JCircleRadar)
|
||||
app.component('JGauge', JGauge)
|
||||
app.component('JColorGauge', JColorGauge)
|
||||
app.component('JScatter', JScatter)
|
||||
app.component('JBubble', JBubble)
|
||||
app.component('DoubleLineBar', DoubleLineBar)
|
||||
app.component('JRose', JRose)
|
||||
app.component('JHorizontalBar', JHorizontalBar)
|
||||
app.component('JArea', JArea)
|
||||
app.component('JBackgroundBar', JBackgroundBar)
|
||||
app.component('JDynamicBar', JDynamicBar)
|
||||
app.component('JMixLineBar', JMixLineBar)
|
||||
app.component('JStackBar', JStackBar)
|
||||
app.component('JStepLine', JStepLine)
|
||||
app.component('JSmoothLine', JSmoothLine)
|
||||
app.component('JProgress', JProgress)
|
||||
app.component('JPictorial', JPictorial)
|
||||
app.component('JPictorialBar', JPictorialBar)
|
||||
app.component('JAreaMap', JAreaMap)
|
||||
app.component('JBubbleMap', JBubbleMap)
|
||||
app.component('JBarMap', JBarMap)
|
||||
app.component('JHeatMap', JHeatMap)
|
||||
app.component('JFlyLineMap', JFlyLineMap)
|
||||
app.component('JTotalFlyLineMap', JTotalFlyLineMap)
|
||||
app.component('JTotalBarMap', JTotalBarMap)
|
||||
|
||||
//非echart组件
|
||||
app.component('JCarousel', JCarousel)
|
||||
app.component('JIframe', JIframe)
|
||||
app.component('JDragEditor', JDragEditor)
|
||||
app.component('JImg', JImg)
|
||||
app.component('JNumber', JNumber)
|
||||
app.component('JText', JText)
|
||||
app.component('JCalendar', JCalendar)
|
||||
app.component('JCurrentTime', JCurrentTime)
|
||||
app.component('JList', JList)
|
||||
app.component('JRadioButton', JRadioButton)
|
||||
app.component('JCommonTable', JCommonTable)
|
||||
app.component('JQuickNav', JQuickNav)
|
||||
app.component('JForm', JForm)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user