ihzero 2024-04-23 07:40:45 +08:00
parent f91386d39d
commit b83976b5d1
17 changed files with 1086 additions and 177 deletions

View File

@ -0,0 +1,134 @@
<template>
<!-- 不能用v-if (i: 每个tab页的专属下标; index: 当前tab的下标 )-->
<view v-show="i === index">
<!-- top="120"下拉布局往下偏移,防止被悬浮菜单遮住 -->
<mescroll-body
:height="`${scrollHeight}px`"
@init="mescrollInit"
:down="downOption"
@down="downCallback"
:up="upOption"
@up="upCallback"
>
<slot :list="list"></slot>
</mescroll-body>
</view>
</template>
<script setup>
import { http } from '@/utils/request'
import { sys } from '@climblee/uv-ui/libs/function'
import { ref, watch, nextTick, computed } from 'vue'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
// import { apiGoods } from '@/api/mock.js'
const props = defineProps({
i: Number,
index: {
type: Number,
default() {
return 0
},
},
top: {
type: Number,
default: 44,
},
apiUrl: {
type: String,
default: '',
},
params: {
type: Object,
default: () => {},
},
})
const scrollHeight = computed(() => {
return sys().screenHeight - props.top
})
// mescroll-bodyonPageScroll, onReachBottom
const { mescrollInit, downCallback, getMescroll } = useMescroll() // mescrollhook
defineExpose({ getMescroll }) // 使refgetMescroll ()
const isAutoInit = props.i === props.index // tab
const downOption = {
auto: isAutoInit, // tab
}
const upOption = {
auto: false, //
noMoreSize: 4, //,;(),; 5
empty: {
tip: '~ 空空如也 ~', //
},
}
const isInit = ref(isAutoInit) // tab
const list = ref([]) //
//
watch(
() => props.index,
(val) => {
if (props.i === val && !isInit.value) mescrollTrigger()
}
)
//
const mescrollTrigger = () => {
isInit.value = true // true
const mescroll = getMescroll()
if (mescroll) {
if (mescroll.optDown.use) {
mescroll.triggerDownScroll()
} else {
mescroll.triggerUpScroll()
}
}
}
/*上拉加载的回调: 其中mescroll.num:当前页 从1开始, mescroll.size:每页数据条数,默认10 */
const upCallback = async (mescroll) => {
await nextTick()
if (!props.apiUrl) return
http.request({
url: props.apiUrl,
method: 'GET',
params: {
per_page: mescroll.size,
page: mescroll.num,
...props.params,
},
})
.then((res) => {
const arr = res.data || [] //
if (mescroll.num == 1) list.value = [] //
list.value = list.value.concat(arr) //
mescroll.endSuccess(arr.length) // ,
})
.catch(() => {
mescroll.endErr() // ,
})
// let word = props.tabs[props.i].name // ,tabtype,status
// apiGoods(mescroll.num, mescroll.size, word)
// .then((res) => {
// const list = res.list || [] //
// if (mescroll.num == 1) goods.value = [] //
// goods.value = goods.value.concat(list) //
// mescroll.endSuccess(list.length) // ,
// })
// .catch(() => {
// mescroll.endErr() // ,
// })
}
//
const emptyClick = () => {
uni.showToast({
title: '点击了按钮,具体逻辑自行实现',
})
}
</script>

View File

@ -0,0 +1,65 @@
<template>
<!-- 当mescroll-body写在子组件时,父页面需引入useMescrollComp.js -->
<mescroll-body
:height="`${scrollHeight}px`"
@init="mescrollInit"
@down="downCallback"
@up="upCallback"
>
<slot :list="list"></slot>
</mescroll-body>
</template>
<script setup>
import { sys } from '@climblee/uv-ui/libs/function'
import { ref, computed, nextTick } from 'vue'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
import { http } from '@/utils/request'
const props = defineProps({
top: {
type: Number,
default: 44,
},
apiUrl: {
type: String,
default: '',
},
params: {
type: Object,
default: () => {},
},
})
const scrollHeight = computed(() => {
return sys().screenHeight - props.top
})
const list = ref([]) //
// mescroll-bodyonPageScroll, onReachBottom
const { mescrollInit, downCallback, getMescroll } = useMescroll() // mescrollhook
// 使refgetMescroll ()
defineExpose({ getMescroll })
// : num: 1, size:,10
const upCallback = async (mescroll) => {
await nextTick()
if (!props.apiUrl) return
http
.get(props.apiUrl, {
params: {
per_page: mescroll.size,
page: mescroll.num,
...props.params,
},
})
.then((res) => {
const curPageData = res.data || [] //
if (mescroll.num == 1) list.value = [] //
list.value = list.value.concat(curPageData) //
mescroll.endSuccess(curPageData.length) //
})
.catch(() => {
mescroll.endErr() //
})
}
</script>

View File

@ -121,6 +121,12 @@
"navigationBarTitleText": "任务提交"
}
},
{
"path": "task_hygienes_submit",
"style": {
"navigationBarTitleText": "清洁任务提交"
}
},
{
"path": "detail",
"style": {
@ -161,6 +167,7 @@
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF",
"navigationStyle": "custom",
"backgroundColorTop":"#FFFFFF",
"app-plus": {
"bounce": "none",
"scrollIndicator": "none"

View File

@ -2,7 +2,11 @@
<view>
<CuNavbar title="报销管理">
<template #right>
<view @click="goPath('/pages/expense-account/submit')" class="text-24rpx text-white">申请</view>
<view
@click="goPath('/pages/expense-account/submit')"
class="text-24rpx text-white"
>申请</view
>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
@ -13,36 +17,63 @@
:list="tabList"
></uv-tabs>
</uv-sticky>
<view class="px-base space-y-20rpx mt-30rpx">
<view
v-for="item in 4"
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
>
<view class="flex items-center justify-between">
<view class="text-30rpx"> 报销申请 </view>
<view class="text-24rpx text-hex-999999">待完成</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">报销金额</view>
<view class="text-primary">12313</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">报销时间</view>
<view class="text-hex-333">2022-01-01</view>
</view>
<view class="text-24rpx text-hex-999999">
<view class="">
<text class="w-140rpx inline-block">报销原因:</text>
<text class="text-hex-333 leading-27rpx">报销原因报销原因报销原因报销原因报销原因报销原因报销原因报销原因报销原因报销原因</text>
<mescroll-body @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="px-base space-y-20rpx mt-30rpx">
<view
v-for="item in list"
:key="item.id"
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
>
<view class="flex items-center justify-between">
<view class="text-30rpx"> {{ item.type.name }} </view>
<view
:style="[
{
color: statusFun(
item.workflow_check.check_status,
'statusExpense',
'color'
),
},
]"
class="text-24rpx"
>{{ item.workflow_check.check_status_text }}</view
>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">报销金额</view>
<view class="text-primary">{{ item.expense }}</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">报销时间</view>
<view class="text-hex-333">{{ timeFormat(item.created_at) }}</view>
</view>
<view class="text-24rpx text-hex-999999">
<view class="">
<text class="w-140rpx inline-block">报销原因:</text>
<text class="text-hex-333 leading-27rpx">{{ item.reason }}</text>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
import { timeFormat } from '@climblee/uv-ui/libs/function'
import statusFun from '@/utils/status'
const { mescrollInit, downCallback, getMescroll } = useMescroll(
onPageScroll,
onReachBottom
)
const list = ref([])
const tabList = ref([
{
name: '我的报销',
@ -52,9 +83,31 @@ const tabList = ref([
},
])
const upCallback = async (mescroll) => {
const { size, num } = mescroll
try {
const resData = await http.get('/reimbursements', {
params: {
per_page: size,
page: num,
},
})
const curPageData = resData || []
if (num === 1) list.value = []
list.value = list.value.concat(curPageData)
mescroll.endSuccess(curPageData.length)
} catch (error) {
console.log(error)
mescroll.endErr() // ,
} finally {
// firstloading.value = false
}
}
const goPath = (url) => {
uni.navigateTo({
url
url,
})
}
</script>

View File

@ -11,40 +11,37 @@
ref="formRef"
labelPosition="left"
>
<uv-form-item
label="报销分类"
required
:borderBottom="true"
prop="content"
>
<uv-input
border="none"
placeholder="请输入报销分类"
v-model="form.content"
></uv-input>
<uv-form-item label="报销分类" required prop="reimbursement_type_id">
<view
@click="openType"
keyName="name"
class="h-full w-full flex justify-end"
>
{{ type.name }}
<uv-icon name="arrow-right"></uv-icon>
</view>
</uv-form-item>
<uv-form-item
:borderBottom="true"
label="报销金额"
required
prop="content"
>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="报销金额" required prop="expense">
<uv-input
border="none"
:border="`none`"
placeholder="请输入报销金额"
v-model="form.content"
type="digit"
input-align="right"
v-model="form.expense"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
label="报销原因"
required
prop="content"
prop="reason"
:borderBottom="true"
labelPosition="top"
>
<uv-textarea
border="none"
v-model="form.content"
:border="`none`"
v-model="form.reason"
placeholder="请输入报销原因"
></uv-textarea>
</uv-form-item>
@ -55,7 +52,14 @@
required
>
<view class="w-full mt-15rpx">
<uv-upload></uv-upload>
<uv-upload
:maxCount="9"
multiple
:fileList="form.photos"
@afterRead="afterRead"
@delete="deletePic"
name="photos"
></uv-upload>
</view>
</uv-form-item>
</uv-form>
@ -65,11 +69,18 @@
<uv-button type="primary" @click="submit"></uv-button>
</view>
<uv-picker
ref="typeRef"
keyName="name"
:columns="[typeList]"
@confirm="typeConfirm"
></uv-picker>
<uv-modal
ref="modalRef"
title="提示"
content="确定提交投诉?"
@confirm="changePassword"
content="确定提交报销申请"
@confirm="onSubmit"
:showCancelButton="true"
></uv-modal>
</view>
@ -79,42 +90,125 @@ import CuNavbar from '@/components/cu-navbar/index'
import Cell from '@/components/cell/index'
import { ref, reactive } from 'vue'
import { http } from '@/utils/request'
import { onLoad } from '@dcloudio/uni-app'
const typeRef = ref(null)
const typeList = ref([])
const type = ref({})
const modalRef = ref(null)
const formRef = ref(null)
const loading = ref(false)
const form = reactive({
content: '',
reimbursement_type_id: '',
expense: '',
reason: '',
photos: [],
})
const rules = reactive({
content: [
{ required: true, message: '请输入投诉内容' },
{ min: 20, message: '至少20字' },
{ max: 200, message: '最多200字' },
reimbursement_type_id: [
{
required: true,
message: '请选择报销分类',
},
],
expense: [{ required: true, message: '请输入报销金额' }],
reason: [{ required: true, message: '请输入报销原因' }],
photos: {
type: 'array',
required: true,
message: '请上传报销凭证',
},
})
onLoad(() => {
getTypes()
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
const resData = await http.post('/reimbursements', {
reimbursement_type_id: form.reimbursement_type_id,
expense: form.expense,
reason: form.reason,
photos: form.photos.map((item) => item.url),
})
uni.showToast({
title: '提交成功',
icon: 'none',
})
formRef.value.resetFields()
uni.$emit('ex:submit', resData)
uni.navigateBack()
} catch (error) {
} finally {
loading.value = false
}
}
const changePassword = () => {
// http
// .post('/auth/profile', {
// password: form.password,
// password_confirmation: form.password2,
// })
// .then((ress) => {
// uni.showToast({
// title: '',
// duration: 2000,
// icon: 'none',
// })
// formRef.value.resetFields()
// })
const afterRead = async (event) => {
let lists = [].concat(event.file)
let fileListLen = form[event.name].length
lists.map((item) => {
form[event.name].push({
...item,
status: 'uploading',
message: '上传中',
})
})
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url)
let item = form[event.name][fileListLen]
form[event.name].splice(
fileListLen,
1,
Object.assign(item, {
status: 'success',
message: '',
url: result,
})
)
fileListLen++
}
}
const uploadFilePromise = (url) => {
return new Promise((resolve, reject) => {
http
.upload('/fileupload', {
filePath: url,
name: 'file',
})
.then((res) => {
resolve(res.url)
})
.catch((err) => {
reject(err)
})
})
}
const deletePic = (event) => {
form[event.name].splice(event.index, 1)
}
const getTypes = () => {
http.get('/keyword?parent_key=reimbursement_type').then((res) => {
typeList.value = res
})
}
const typeConfirm = ({ value }) => {
type.value = value[0]
form.reimbursement_type_id = type.value.id
}
const openType = () => {
typeRef.value.open()
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<view>
<uv-sticky bgColor="#fff">
<view class="flex-center h-88rpx">
<view class="flex-center h-44px">
<view class="flex-center flex-1" @click="selectMenu({ name: 'shore' })">
<view>全部区域</view>
<uv-icon

View File

@ -0,0 +1,50 @@
<template>
<view
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
@click.stop="onClick"
>
<view class="flex items-center justify-between">
<view class="text-30rpx">{{ item.name }}</view>
<view
:style="{
color: statusFun(item.taskable.status, item.taskable_type, 'color'),
}"
class="text-24rpx"
>{{ statusFun(item.taskable.status, item.taskable_type, 'name') }}</view
>
</view>
<view class="text-24rpx text-hex-999999">
任务时间{{ timeFormat(item.start_at, 'yyyy年mm月dd日') }} -
{{ timeFormat(item.end_at, 'yyyy年mm月dd日') }}
</view>
<template v-if="item.taskable.status == 2">
<view class="py-10rpx">
<uv-line></uv-line>
</view>
<view class="flex justify-end">
<view @click.stop="onTask">
<uv-button type="primary" size="mini">去完成</uv-button>
</view>
</view>
</template>
</view>
</template>
<script setup>
import statusFun from '@/utils/status'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
const props = defineProps({
item: Object,
})
const onClick = () => {
const type = props.item.taskable_type
console.log(type)
}
const onTask = (e) => {
const type = props.item.taskable_type
uni.navigateTo({
url: `/pages/task/${type}_submit?id=${props.item.id}`,
})
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<view>
<CuNavbar title="我的任务"></CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
></uv-tabs>
</uv-sticky>
<mescroll-body @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="px-base space-y-20rpx mt-30rpx">
<view
v-for="item in 4"
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
>
<view class="text-30rpx">月度清洁任务</view>
<view class="text-24rpx text-hex-999999">
任务时间2022年03月01号 - 2022年03月31号
</view>
<view class="text-24rpx text-hex-999999">待完成</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script setup>
import { http } from '@/utils/request'
import CuNavbar from '@/components/cu-navbar/index'
import { computed, reactive, ref } from 'vue'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
const { mescrollInit, downCallback, getMescroll } = useMescroll(
onPageScroll,
onReachBottom
)
const list = ref([])
const tabList = ref([
{
name: '任务列表',
apiUrl: '/tasks',
},
{
name: '任务审核',
apiUrl: '/workflow',
params:{
}
},
])
const upCallback = async (mescroll) => {
const { size, num } = mescroll
try {
const resData = await http.get('/tasks', {
params: {
per_page: size,
page: num,
},
})
const curPageData = resData.data || []
if (num === 1) list.value = []
list.value = list.value.concat(curPageData)
mescroll.endSuccess(curPageData.length)
} catch (error) {
console.log(error)
mescroll.endErr() // ,
} finally {
// firstloading.value = false
}
}
</script>

View File

@ -3,32 +3,72 @@
<CuNavbar title="我的任务"></CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
height="44"
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
:current="tabIndex"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<view class="px-base space-y-20rpx mt-30rpx">
<view v-for="item in 4" class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx">
<view class="text-30rpx">月度清洁任务</view>
<view class="text-24rpx text-hex-999999">
任务时间2022年03月01号 - 2022年03月31号
<MescrollItem
ref="mescrollItem0"
:top="88"
:i="0"
:index="tabIndex"
:apiUrl="tabList[0].apiUrl"
>
<template v-slot="{ list }">
<view class="space-y-15rpx p-base">
<view v-for="(item, i) in list" :key="i">
<Item :item="item"></Item>
</view>
</view>
<view class="text-24rpx text-hex-999999">待完成</view>
</view>
</view>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:i="1"
:top="88"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
:params="tabList[1].params"
></MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from 'vue'
import { computed, reactive, ref } from 'vue'
import Item from './components/item.vue'
import { onPageScroll, onReachBottom, onShow } from '@dcloudio/uni-app'
import useMescrollMore from '@/uni_modules/mescroll-uni/hooks/useMescrollMore.js'
import MescrollItem from '@/components/mescroll-api/more.vue'
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
const tabList = ref([
{
name: '任务列表',
apiUrl: '/tasks',
},
{
name: '任务审核',
apiUrl: '/workflow',
params: {},
},
])
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,154 @@
<template>
<view class="p-base">
<CuNavbar title="清洁任务提交"></CuNavbar>
<view class="card-shadow px-base">
<uv-form
labelPosition="left"
:model="form"
:rules="rules"
ref="formRef"
errorType="toast"
labelWidth="150rpx"
>
<uv-form-item required label="清洁范围" prop="description">
<uv-input
placeholder="请输入清洁范围"
inputAlign="right"
:border="`none`"
v-model="form.description"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
label="报销凭证"
labelPosition="top"
prop="photos"
required
>
<view class="w-full mt-15rpx">
<uv-upload
:maxCount="9"
multiple
:fileList="form.photos"
@afterRead="afterRead"
@delete="deletePic"
name="photos"
></uv-upload>
</view>
</uv-form-item>
</uv-form>
</view>
<view class="mt-100rpx">
<uv-button type="primary" @click="submit"></uv-button>
</view>
<uv-modal
ref="modalRef"
title="提示"
content="确定提交吗?"
@confirm="onSubmit"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { http } from '@/utils/request'
const formRef = ref(null)
const modalRef = ref(null)
const id = ref(0)
const loading = ref(false)
const form = reactive({
description: '',
photos: [],
})
const rules = reactive({
description: [{ required: true, message: '请输入清洁范围' }],
photos: {
type: 'array',
required: true,
message: '请上传报销凭证',
},
})
onLoad((options) => {
id.value = options.id
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
const resData = await http.post(`/tasks/${id.value}/submit`, {
id: id.value,
description: form.description,
photos: form.photos.map((item) => item.url),
})
uni.showToast({
title: '提交成功',
icon: 'none',
})
formRef.value.resetFields()
uni.$emit('ex:submit', resData)
uni.navigateBack()
} catch (error) {
} finally {
loading.value = false
}
}
const afterRead = async (event) => {
let lists = [].concat(event.file)
let fileListLen = form[event.name].length
lists.map((item) => {
form[event.name].push({
...item,
status: 'uploading',
message: '上传中',
})
})
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url)
let item = form[event.name][fileListLen]
form[event.name].splice(
fileListLen,
1,
Object.assign(item, {
status: 'success',
message: '',
url: result,
})
)
fileListLen++
}
}
const uploadFilePromise = (url) => {
return new Promise((resolve, reject) => {
http
.upload('/fileupload', {
filePath: url,
name: 'file',
})
.then((res) => {
resolve(res.url)
})
.catch((err) => {
reject(err)
})
})
}
const deletePic = (event) => {
form[event.name].splice(event.index, 1)
}
</script>

View File

@ -0,0 +1,40 @@
<template>
<view class="flex rounded-19rpx box-content card" @click="goPage(`/pages/user/detail?id=${item.id}`)">
<view class="rounded-full w-100rpx h-100rpx overflow-hidden">
<image class="w-full h-full" :src="item.avatar"></image>
</view>
<view class="flex-1 ml-20rpx flex flex-col justify-between">
<view class="flex text-sm">
<view>{{ item.name }}</view>
<template v-if="item.jobs">
<uv-tags
v-for="job in item.jobs"
class="ml-20rpx"
:text="job.name"
:key="job.id"
plain
size="mini"
type="primary"
></uv-tags>
</template>
</view>
<view class="text-24rpx text-hex-999">
<view class="">电话{{ item.phone }}</view>
<view class="" v-if="item.store">{{ item.store.address }}</view>
</view>
</view>
</view>
</template>
<script setup>
defineProps({
item: {
type: Object,
default: () => ({}),
},
})
const goPage = (url) => {
uni.navigateTo({
url,
})
}
</script>

View File

@ -38,13 +38,27 @@
cancelText="取消"
>
</uv-action-sheet>
<uv-modal
ref="modalRef"
:title="modalOptin.title"
:content="modalOptin.content"
@confirm="modalOptin.confirm"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { ref, reactive } from 'vue'
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { onLoad, onShow } from '@dcloudio/uni-app'
const modalRef = ref(null)
const modalOptin = reactive({
title: '提示',
content: '',
confirm: () => {},
})
const actionSheet = ref(null)
const id = ref(null)
const detail = ref({})
@ -72,7 +86,6 @@ onShow(() => {
const getDetail = () => {
http.get(`/hr/employee/${id.value}`).then((res) => {
console.log(res)
detail.value = res
})
}
@ -86,6 +99,36 @@ const select = (e) => {
uni.navigateTo({
url: `/pages/user/update?id=${id.value}`,
})
} else if (value === 'quit') {
modalOptin.content = '是否确认离职该员工?'
modalOptin.confirm = onQuick
modalRef.value.open()
} else if (value === 'delete') {
modalOptin.content = '是否确认删除该员工?'
modalOptin.confirm = onDelete
modalRef.value.open()
}
}
const onDelete = () => {
http.delete(`/hr/employee/${id.value}`).then((res) => {
uni.$emit('user:onRefresh')
uni.showToast({
title: '删除成功',
duration: 2000,
icon: 'none',
})
uni.navigateBack()
})
}
const onQuick = () => {
http.post(`/hr/employee/${id.value}/leave`).then((res) => {
uni.showToast({
title: '离职成功',
duration: 2000,
icon: 'none',
})
uni.$emit('user:onRefresh')
uni.navigateBack()
})
}
</script>

View File

@ -0,0 +1,128 @@
<template>
<view>
<CuNavbar title="员工管理">
<template #right>
<text @click="goPage('/pages/user/update')" class="text-white"
>添加</text
>
</template>
</CuNavbar>
<StoreDropDown></StoreDropDown>
<mescroll-body @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="mt-15rpx px-base">
<!-- <uv-swipe-action> -->
<view class="space-y-15rpx">
<view
class="rounded-19rpx card p-0 overflow-hidden"
v-for="(item, i) in list"
:key="i"
@click="goPage(`/pages/user/detail?id=${item.id}`)"
>
<!-- <uv-swipe-action-item :options="options"> -->
<view class="flex rounded-19rpx box-content card">
<view class="rounded-4rpx w-100rpx h-100rpx">
<image class="w-full h-full" :src="item.avatar"></image>
</view>
<view
class="flex-1 ml-20rpx flex flex-col justify-between py-10rpx"
>
<view class="flex items-center text-sm">
<view>{{ item.name }}</view>
<template v-if="item.jobs">
<uv-tags
v-for="job in item.jobs"
class="ml-20rpx"
:text="job.name"
:key="job.id"
plain
size="mini"
type="primary"
></uv-tags>
</template>
</view>
<view class="flex justify-between">
<view class="text-28rpx text-hex-999 text-xs">{{
item.phone
}}</view>
<view v-if="item.store">{{ item.store.address }}</view>
</view>
</view>
</view>
<!-- </uv-swipe-action-item> -->
</view>
</view>
<!-- </uv-swipe-action> -->
</view>
</mescroll-body>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { computed, reactive, ref } from 'vue'
import StoreDropDown from '@/pages/home/components/store-drop-down/index.vue'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
import { onShow } from '@dcloudio/uni-app'
const { mescrollInit, downCallback, getMescroll } = useMescroll(
onPageScroll,
onReachBottom
)
const firstloading = ref(true)
const list = ref([])
const options = [
{
text: '删除',
style: {
backgroundColor: '#f56c6c',
},
},
{
text: '修改',
style: {
backgroundColor: '#3c9cff',
},
},
{
text: '离职',
style: {
backgroundColor: '#f9ae3d',
},
},
]
onShow(() => {
if (!firstloading.value) {
getMescroll().resetUpScroll()
}
})
const upCallback = async (mescroll) => {
const { size, num } = mescroll
try {
const resData = await http.get('/hr/employee', {
params: {
per_page: size,
page: num,
},
})
const curPageData = resData.data || []
if (num === 1) list.value = []
list.value = list.value.concat(curPageData)
mescroll.endSuccess(curPageData.length)
} catch (error) {
console.log(error)
mescroll.endErr() // ,
} finally {
firstloading.value = false
}
}
const goPage = (url) => {
uni.navigateTo({
url,
})
}
</script>

View File

@ -8,110 +8,44 @@
</template>
</CuNavbar>
<StoreDropDown></StoreDropDown>
<mescroll-body @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="mt-15rpx px-base">
<!-- <uv-swipe-action> -->
<view class="space-y-15rpx">
<view
class="rounded-19rpx card p-0 overflow-hidden"
v-for="(item, i) in list"
:key="i"
@click="goPage(`/pages/user/detail?id=${item.id}`)"
>
<!-- <uv-swipe-action-item :options="options"> -->
<view class="flex rounded-19rpx box-content card">
<view class="rounded-4rpx w-100rpx h-100rpx">
<image class="w-full h-full" :src="item.avatar"></image>
</view>
<view
class="flex-1 ml-20rpx flex flex-col justify-between py-10rpx"
>
<view class="flex items-center text-sm">
<view>{{ item.name }}</view>
<template v-if="item.jobs">
<uv-tags
v-for="job in item.jobs"
class="ml-20rpx"
:text="job.name"
:key="job.id"
plain
size="mini"
type="primary"
></uv-tags>
</template>
</view>
<view class="flex justify-between">
<view class="text-28rpx text-hex-999 text-xs">{{
item.phone
}}</view>
<view v-if="item.store">{{ item.store.address }}</view>
</view>
</view>
</view>
<!-- </uv-swipe-action-item> -->
<MescrollApiOne
:top="88"
ref="mescrollItem"
apiUrl="/hr/employee"
:params="params"
>
<template v-slot="{ list }">
<view class="space-y-15rpx p-base">
<view v-for="(item, i) in list" :key="i">
<Item :item="item"></Item>
</view>
</view>
<!-- </uv-swipe-action> -->
</view>
</mescroll-body>
</template>
</MescrollApiOne>
</view>
</template>
<script setup>
import { ref } from 'vue'
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { computed, reactive, ref } from 'vue'
import StoreDropDown from '@/pages/home/components/store-drop-down/index.vue'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
import Item from './components/item.vue'
import MescrollApiOne from '@/components/mescroll-api/one'
import { onPageScroll, onReachBottom, onLoad } from '@dcloudio/uni-app'
import useMescrollComp from '@/uni_modules/mescroll-uni/hooks/useMescrollComp.js'
const { mescrollItem } = useMescrollComp(onPageScroll, onReachBottom)
const params = ref(null)
const { mescrollInit, downCallback, getMescroll } = useMescroll(
onPageScroll,
onReachBottom
)
onLoad(()=>{
uni.$on('user:onRefresh',onRefresh)
})
const list = ref([])
const options = [
{
text: '删除',
style: {
backgroundColor: '#f56c6c',
},
},
{
text: '修改',
style: {
backgroundColor: '#3c9cff',
},
},
{
text: '离职',
style: {
backgroundColor: '#f9ae3d',
},
},
]
const upCallback = async (mescroll) => {
const { size, num } = mescroll
try {
const resData = await http.get('/hr/employee', {
params: {
per_page: size,
page: num,
},
})
const curPageData = resData.data || []
if (num === 1) list.value = []
list.value = list.value.concat(curPageData)
mescroll.endSuccess(curPageData.length)
} catch (error) {
console.log(error)
mescroll.endErr() // ,
}
const onRefresh = () => {
// params.value = {
// city_code: 11,
// store_id: 12,
// }
mescrollItem.value.getMescroll().resetUpScroll()
}
const goPage = (url) => {
uni.navigateTo({
url,

View File

@ -36,7 +36,7 @@
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="登录用户名" prop="name">
<uv-form-item required label="登录用户名" prop="username">
<uv-input
placeholder="请输入登录用户名"
inputAlign="right"
@ -56,6 +56,9 @@
>
</uv-input>
</uv-form-item>
<uv-form-item :required="!isEdit" label="门店" prop="password">
</uv-form-item>
</uv-form>
</view>
<view class="mt-100rpx">
@ -115,7 +118,7 @@ const rules = computed(() => {
})
onLoad((options) => {
id.value = options.id
getDetail()
if(isEdit.value) getDetail()
})
const isEdit = computed(() => !!id.value)
const title = computed(() => (isEdit.value ? '员工修改' : '员工添加'))
@ -142,6 +145,7 @@ const updateUser = async () => {
await http.put(`/hr/employee/${id.value}`, {
...form,
})
uni.$emit('user:onRefresh')
uni.navigateBack()
formRef.value.resetFields()
uni.showToast({

View File

@ -44,7 +44,17 @@ http.interceptors.request.use((config) => {
// 必须使用异步函数,注意
http.interceptors.response.use(async (response) => {
// 处理响应结果
const { statusCode, data } = response
if (statusCode !== 200 && statusCode !== 201) {
uni.showToast({
title: data.message || '服务器错误',
icon: 'none'
})
return Promise.reject(response)
}
const { isTransformResponse = true, isReturnNativeResponse } = response.config.custom;
// 是否返回原生响应头
if (isReturnNativeResponse) {

View File

@ -0,0 +1,79 @@
const data = {
statusExpense: [{
value: 1,
name: '待提审',
color: '#f56c6c'
}, {
value: 2,
name: '审核中',
color: '#f56c6c'
}, {
value: 3,
name: '审核通过',
color: '#3c9cff'
}, {
value: 4,
name: '未通过',
color: '#999999'
}, {
value: 5,
name: '已取消',
color: '#999999'
}],
//清洁任务
task_hygienes: [{
value: 1,
name: '未开始',
color: '#f56c6c'
}, {
value: 2,
name: '待完成',
color: '#f56c6c'
}, {
value: 3,
name: '审核中',
color: '#3c9cff'
}, {
value: 4,
name: '未通过',
color: '#999999'
}, {
value: 5,
name: '已完成',
color: '#3c9cff'
}, {
value: 6,
name: '未完成',
color: '#999999'
}],
//总账录入
task_ledgers: [{
value: 1,
name: '未开始',
color: '#f56c6c'
}, {
value: 2,
name: '待完成',
color: '#f56c6c'
}, {
value: 3,
name: '已完成',
color: '#3c9cff'
}, {
value: 4,
name: '未完成',
color: '#999999'
}]
}
export default function (value, status, key) {
if (!status) {
return value
}
const item = data[status]?.find((item) => item.value === value)
if (item && key) {
return item[key] ?? value
}
return item ?? value
}