main
ihzero 2024-04-24 05:20:01 +08:00
commit 6e2cd9fe2a
159 changed files with 67596 additions and 0 deletions

0
.env 100644
View File

5
.env.development 100644
View File

@ -0,0 +1,5 @@
VITE_COMMON_API_PREFIX = /api
VITE_COMMON_API_URL = http://store-manage.hmily.club

3
.env.production 100644
View File

@ -0,0 +1,3 @@
VITE_COMMON_API_PREFIX = /api
VITE_COMMON_API_URL = http://store-manage.hmily.club

3
.env.test 100644
View File

@ -0,0 +1,3 @@
VITE_COMMON_API_PREFIX = /api
VITE_COMMON_API_URL = http://store-manage.hmily.club

21
.gitignore vendored 100644
View File

@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

20
index.html 100644
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

21540
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

77
package.json 100644
View File

@ -0,0 +1,77 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@climblee/uv-ui": "^1.1.20",
"@dcloudio/uni-app": "3.0.0-3090920231225001",
"@dcloudio/uni-app-plus": "3.0.0-3090920231225001",
"@dcloudio/uni-components": "3.0.0-3090920231225001",
"@dcloudio/uni-h5": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-alipay": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-baidu": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-jd": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-lark": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-qq": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-xhs": "3.0.0-3090920231225001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090920231225001",
"@qiun/ucharts": "^2.5.0-20230101",
"luch-request": "^3.1.1",
"pinia": "2.0.33",
"pinia-plugin-persistedstate": "^3.2.1",
"vue": "^3.2.45",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-3090920231225001",
"@dcloudio/uni-cli-shared": "3.0.0-3090920231225001",
"@dcloudio/uni-stacktracey": "3.0.0-3090920231225001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090920231225001",
"@vue/runtime-core": "^3.2.45",
"sass": "^1.71.1",
"unocss": "^0.58.5",
"unocss-applet": "^0.7.8",
"vite": "4.0.3"
}
}

10
shims-uni.d.ts vendored 100644
View File

@ -0,0 +1,10 @@
/// <reference types='@dcloudio/types' />
import 'vue'
declare module '@vue/runtime-core' {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {
}
}

19
src/App.vue 100644
View File

@ -0,0 +1,19 @@
<script>
// import { useUserStoreWithOut } from "@/store/modules/user";
export default {
onLaunch: function () {
console.log('App Launch')
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
},
}
</script>
<style lang="scss">
@import '@/static/css/index.scss';
@import '@climblee/uv-ui/index.scss';
</style>

View File

@ -0,0 +1,52 @@
<template>
<view
class="flex items-center h-92rpx pl-base pr-15rpx rounded-19rpx bg-white"
:class="[{ 'card-shadow': shadow }]"
@click="$emit('onClick')"
>
<view
class="text-[#333333] font-400 text-27rpx"
:style="[
{ width: `${getPx(titleWidth)}px`, fontSize: `${getPx(textSize)}px` },
]"
>{{ title }}</view
>
<view class="flex-1">
<slot></slot>
</view>
<view class="h-full flex-center">
<slot name="right-icon">
<image
v-if="isLink"
class="w-26rpx h-26rpx"
src="@/static/images/me_icon_more_def.svg"
></image>
</slot>
</view>
</view>
</template>
<script setup>
import { getPx } from '@climblee/uv-ui/libs/function/index.js'
const props = defineProps({
title: {
type: String,
},
isLink: {
type: Boolean,
default: true,
},
shadow: {
type: Boolean,
default: true,
},
titleWidth: {
type: String,
default: '1130rpx',
},
textSize: {
type: String,
default: '27rpx',
},
})
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,119 @@
<template>
<uv-navbar
:fixed="fixed"
:bgColor="bgColor"
:leftIcon="leftIcon"
:title="title"
:titleStyle="{...titleStyle}"
:placeholder="placeholder"
@leftClick="onLeftClick"
z-index="1000"
>
<template #left>
<slot name="left">
<image
v-if="isBack && !leftIcon"
class="w-54rpx h-54rpx"
src="/static/images/icon_back_def.svg"
></image>
</slot>
</template>
<template #center>
<slot name="center"></slot>
</template>
<template #right>
<view :style="[{ paddingRight: addUnit(paddingRight) }]">
<slot name="right"></slot>
</view>
</template>
</uv-navbar>
</template>
<script setup>
let menuButtonInfo = {}
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// #endif
import { computed } from 'vue'
import { addUnit, sys } from '@climblee/uv-ui/libs/function/index.js'
const props = defineProps({
fixed: {
type: Boolean,
default: true,
},
isBack: {
type: Boolean,
default: true,
},
autoBack: {
type: Boolean,
default: true,
},
placeholder: {
type: Boolean,
default: true,
},
bgColor: {
type: String,
default: '#ee2c37',
},
imgMode: {
type: String,
default: 'aspectFill',
},
safeAreaInsetTop: {
type: Boolean,
default: true,
},
height: {
type: [String, Number],
default: '44px',
},
title: {
type: String,
default: '',
},
leftIcon: {
type: String,
default: '',
},
titleStyle: {
type: Object,
default: () => {
return {
color: '#fff',
}
},
},
})
const emit = defineEmits(['leftClick'])
const paddingRight = computed(() => {
let rightButtonWidth = 0
rightButtonWidth =
sys().windowWidth - (menuButtonInfo?.left ?? sys().windowWidth)
return `${rightButtonWidth}px`
})
const onLeftClick = () => {
emit('leftClick')
if (props.autoBack) {
// #ifdef H5
let canNavBack = getCurrentPages()
if (canNavBack && canNavBack.length > 1) {
uni.navigateBack({
delta: 1,
})
} else {
history.back()
}
// #endif
// #ifndef H5
uni.navigateBack()
// #endif
}
}
</script>

View File

@ -0,0 +1,66 @@
<template>
<view>
<view class="flex justify-end text-white" @click="onClick">
<view>{{ valueFormat }}</view>
<uv-icon color="white" size="16rpx" name="arrow-down-fill"></uv-icon>
</view>
<uv-datetime-picker
ref="datetimePicker"
v-model="value"
:mode="mode"
@confirm="confirm"
:maxDate="Number(new Date())"
:minDate="Number(new Date(2020, 0, 1))"
>
</uv-datetime-picker>
</view>
</template>
<script setup>
import { ref, watch, watchEffect, computed } from 'vue'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
const props = defineProps({
modelValue: {
type: [String, Number],
default: null,
},
mode: {
type: String,
default: 'year-month',
},
formatStr: {
type: String,
default: 'yyyy年mm月',
},
})
const datetimePicker = ref(null)
const value = ref(props.modelValue || Number(new Date()))
const valueFormat = computed(() => {
return timeFormat(value.value, props.formatStr)
})
const emit = defineEmits(['update:modelValue','confirm'])
const confirm = (e) => {
value.value = e.value
emit('confirm', e)
}
const onClick = () => {
datetimePicker.value.open()
}
watchEffect(() => {
value.value = props.modelValue || Number(new Date())
})
watch(
() => value.value,
(val) => {
emit('update:modelValue', val)
}
)
</script>

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

@ -0,0 +1,32 @@
<template>
<view class="flex items-center justify-between">
<view class="title" :style="{ fontSize: `${getPx(textSize)}px` }">{{
title
}}</view>
<view>
<slot name="right"></slot>
</view>
</view>
</template>
<script setup>
import { getPx } from '@climblee/uv-ui/libs/function/index.js'
const props = defineProps({
title: {
type: String,
default: '',
},
textSize: {
type: String,
default: '27rpx',
},
})
</script>
<style lang="scss">
.title {
@apply relative pl-20rpx;
&::after {
content: '';
@apply absolute left-0 bottom-4rpx top-4rpx w-3px bg-primary;
}
}
</style>

View File

@ -0,0 +1,21 @@
export const useGlobSetting = () => {
const ENV = import.meta.env
const { VITE_COMMON_API_PREFIX, VITE_COMMON_API_URL } = ENV
let apiUrl = ''
// #ifdef MP-WEIXIN || APP-PLUS
apiUrl += VITE_COMMON_API_URL
// #endif
// #ifdef H5
apiUrl += VITE_COMMON_API_PREFIX
// #endif
return {
urlPrefix: VITE_COMMON_API_PREFIX,
api: VITE_COMMON_API_URL,
apiUrl: apiUrl
}
}

14
src/main.js 100644
View File

@ -0,0 +1,14 @@
import {
createSSRApp
} from "vue";
import App from "./App.vue";
import 'virtual:uno.css'
import { setupStore } from '@/store';
export function createApp() {
const app = createSSRApp(App);
setupStore(app);
return {
app,
};
}

72
src/manifest.json 100644
View File

@ -0,0 +1,72 @@
{
"name" : "",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics": {
"enable": false
},
"vueVersion" : "3"
}

316
src/pages copy.json 100644
View File

@ -0,0 +1,316 @@
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/home/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/revert/index",
"style": {
"navigationBarTitleText": "上报"
}
},
{
"path": "pages/statement/index",
"style": {
"navigationBarTitleText": "报表"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"subPackages": [
{
"root": "pages/data",
"pages": [
{
"path": "brokerage/index",
"style": {
"navigationBarTitleText": "提成数据"
}
},
{
"path": "performance/index",
"style": {
"navigationBarTitleText": "业绩数据"
}
},
{
"path": "upload/index",
"style": {
"navigationBarTitleText": "上传数据"
}
}
]
},
{
"root": "pages/user",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "员工管理"
}
},
{
"path": "update",
"style": {
"navigationBarTitleText": "修改信息"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "员工详情"
}
}
]
},
{
"root": "pages/setting",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "complain",
"style": {
"navigationBarTitleText": "举报投诉"
}
},
{
"path": "suggestion",
"style": {
"navigationBarTitleText": "意见箱"
}
}
]
},
{
"root": "pages/task",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "任务列表"
}
},
{
"path": "submit",
"style": {
"navigationBarTitleText": "任务提交"
}
},
{
"path": "task_hygienes_submit",
"style": {
"navigationBarTitleText": "清洁任务提交"
}
},
{
"path": "task_ledgers_submit",
"style": {
"navigationBarTitleText": "数据上报"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "任务详情"
}
}
]
},
{
"root": "pages/expense-account",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "报销管理"
}
},
{
"path": "submit",
"style": {
"navigationBarTitleText": "报销提交"
}
}
]
},
{
"root": "pages/work",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "升职申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "升职申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "升职申请"
}
}
]
},
{
"root": "pages/make-card",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "补卡申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "补卡申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "补卡审核"
}
}
]
},
{
"root": "pages/ask-leave",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "请假申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "请假申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "请假详情"
}
}
]
},
{
"root": "pages/train-books",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "培训课件"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "培训课件"
}
}
]
},
{
"root": "pages/examination",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "培训考试"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "培训考试"
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF",
"navigationStyle": "custom",
"backgroundColorTop": "#FFFFFF",
"app-plus": {
"bounce": "none",
"scrollIndicator": "none"
}
},
"tabBar": {
"color": "#333",
"selectedColor": "#ff3c2a",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/home/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "首页"
},
{
"pagePath": "pages/revert/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "上报"
},
{
"pagePath": "pages/statement/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "报表"
},
{
"pagePath": "pages/mine/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "我的"
}
]
},
"easycom": {
"autoscan": true,
"custom": {
"^uv-(.*)": "@climblee/uv-ui/components/uv-$1/uv-$1.vue"
}
}
}

384
src/pages.json 100644
View File

@ -0,0 +1,384 @@
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/home/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/revert/index",
"style": {
"navigationBarTitleText": "上报"
}
},
{
"path": "pages/statement/index",
"style": {
"navigationBarTitleText": "报表"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"subPackages": [
{
"root": "pages/data",
"pages": [
{
"path": "brokerage/index",
"style": {
"navigationBarTitleText": "提成数据"
}
},
{
"path": "performance/index",
"style": {
"navigationBarTitleText": "业绩数据"
}
},
{
"path": "upload/index",
"style": {
"navigationBarTitleText": "上传数据"
}
}
]
},
{
"root": "pages/user",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "员工管理"
}
},
{
"path": "update",
"style": {
"navigationBarTitleText": "修改信息"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "员工详情"
}
}
]
},
{
"root": "pages/setting",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "complain",
"style": {
"navigationBarTitleText": "举报投诉"
}
},
{
"path": "suggestion",
"style": {
"navigationBarTitleText": "意见箱"
}
}
]
},
{
"root": "pages/task",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "任务列表"
}
},
{
"path": "submit",
"style": {
"navigationBarTitleText": "任务提交"
}
},
{
"path": "task_hygienes_submit",
"style": {
"navigationBarTitleText": "清洁任务提交"
}
},
{
"path": "task_ledgers_submit",
"style": {
"navigationBarTitleText": "数据上报"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "任务详情"
}
}
]
},
{
"root": "pages/expense-account",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "报销管理"
}
},
{
"path": "submit",
"style": {
"navigationBarTitleText": "报销提交"
}
}
]
},
{
"root": "pages/work",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "升职申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "升职申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "升职申请"
}
}
]
},
{
"root": "pages/make-card",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "补卡申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "补卡申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "补卡审核"
}
}
]
},
{
"root": "pages/ask-leave",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "请假申请"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "请假申请"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "请假详情"
}
}
]
},
{
"root": "pages/business",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "出差报备"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "出差报备"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "出差详情"
}
}
]
},
{
"root": "pages/overtime",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "加班报备"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "加班报备"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "加班详情"
}
}
]
},
{
"root": "pages/contract",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "加班报备"
}
},
{
"path": "create",
"style": {
"navigationBarTitleText": "加班报备"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "加班详情"
}
}
]
},{
"root": "pages/train-books",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "培训课件"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "培训课件"
}
}
]
},
{
"root": "pages/examination",
"pages": [
{
"path": "index",
"style": {
"navigationBarTitleText": "培训考试"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "培训考试"
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF",
"navigationStyle": "custom",
"backgroundColorTop": "#FFFFFF",
"app-plus": {
"bounce": "none",
"scrollIndicator": "none"
}
},
"tabBar": {
"color": "#333",
"selectedColor": "#ff3c2a",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/home/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "首页"
},
{
"pagePath": "pages/revert/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "上报"
},
{
"pagePath": "pages/statement/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "报表"
},
{
"pagePath": "pages/mine/index",
"selectedIconPath": "static/images/home.png",
"iconPath": "static/images/home.png",
"text": "我的"
}
]
},
"easycom": {
"autoscan": true,
"custom": {
"^uv-(.*)": "@climblee/uv-ui/components/uv-$1/uv-$1.vue"
}
}
}

View File

@ -0,0 +1,36 @@
<template>
<view class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx" @click="goPath">
<view class="text-30rpx"> 请假申请</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">请假类型</view>
<view class="">{{ item.type.name }}</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">请假事由</view>
<view class="">{{ item.reason }}</view>
</view>
<view class="flex items-center text-hex-999999 flex">
<view class="text-24rpx w-140rpx"> 申请时间</view>
<view class="text-24rpx">{{ timeFormat(item.created_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<view
:style="{
color: statusFun(item.workflow_check.check_status, 'workflow_check', 'color')
}"
class="text-24rpx"
>{{ statusFun(item.workflow_check.check_status, "workflow_check", "name") }}</view
>
</view>
</template>
<script setup>
import statusFun from "@/utils/status"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const props = defineProps({
item: Object
})
const goPath = () => {
uni.navigateTo({
url: `/pages/ask-leave/detail?id=${props.item.id}`
})
}
</script>

View File

@ -0,0 +1,189 @@
<template>
<view>
<CuNavbar title="请假申请">
<template #right>
<view class="text-24rpx text-white" @click="submit"></view>
</template>
</CuNavbar>
<view class="card-shadow px-base">
<uv-form labelPosition="left" :model="form" :rules="rules" ref="formRef" errorType="toast" labelWidth="250rpx">
<uv-form-item required label="请假类别" prop="type_id">
<uv-input
placeholder="请选择"
@click="openPicker"
readonly
inputAlign="right"
:border="`none`"
v-model="form.type_id"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="请假开始时间" prop="start_at">
<uv-input
placeholder="请选择日期"
readonly
@click="openStartDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.start_at"
>
</uv-input>
</uv-form-item>
<uv-form-item required label="请假结束时间" prop="end_at">
<uv-input
placeholder="请选择日期"
readonly
@click="openEndDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.end_at"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="请假理由" prop="reason" labelPosition="top">
<uv-textarea v-model="form.reason" count placeholder="请输入" :border="`none`" :maxlength="200"></uv-textarea>
</uv-form-item>
</uv-form>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-datetime-picker
placeholder="请选择日期"
v-model="startValue"
ref="dateStartPicker"
mode="datetime"
@confirm="confirmStartDatePicker"
>
</uv-datetime-picker>
<uv-datetime-picker
v-model="endValue"
placeholder="请选择日期"
ref="dateEndPicker"
mode="datetime"
@confirm="confirmEndDatePicker"
>
</uv-datetime-picker>
<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"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const columns = ref([])
const pickerData = ref([])
const formRef = ref(null)
const dateStartPicker = ref(null)
const dateEndPicker = ref(null)
const pickerRef = ref(null)
const modalRef = ref(null)
const startValue = ref(Number(new Date()))
const endValue = ref(Number(new Date()))
const id = ref(0)
const loading = ref(false)
const form = reactive({
start_at: "",
end_at: "",
reason: "",
type_id: ""
})
const openPicker = () => {
pickerRef.value.open()
}
const openStartDatePicker = () => {
dateStartPicker.value.open()
}
const openEndDatePicker = () => {
dateEndPicker.value.open()
}
const confirmStartDatePicker = e => {
form.start_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const confirmEndDatePicker = e => {
form.end_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const confirmPicker = e => {
form.type_id = e.value[0]
}
const rules = reactive({
start_at: [{ required: true, message: "请选择时间" }],
end_at: [{ required: true, message: "请选择时间" }],
reason: [{ required: true, message: "请输入请假理由" }],
type_id: [{ required: true, message: "请选择请假类别" }]
})
onLoad(options => {
http
.request({
url: `/keywords?parent_key=holiday_type`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
let names = res.map(item => item.name)
columns.value = [names]
pickerData.value = res
})
id.value = options.id
if (id.value) {
http
.request({
url: `/hr/holidays/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
startValue.value = res.start_at * 1000
endValue.value = res.end_at * 1000
form.start_at = timeFormat(res.start_at, "yyyy-mm-dd hh:MM:ss")
form.end_at = timeFormat(res.end_at, "yyyy-mm-dd hh:MM:ss")
form.reason = res.reason
form.type_id = res.type.name
})
}
})
const submit = () => {
formRef.value.validate().then(res => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
let url = id.value ? `/hr/holidays/${id.value}` : "/hr/holidays"
let method = id.value ? "PUT" : "POST"
await http.request({
url: url,
method: method,
header: {
Accept: "application/json"
},
data: {
start_at: form.start_at,
type_id: pickerData.value.find(item => item.name === form.type_id).id,
reason: form.reason,
end_at: form.end_at
}
})
uni.showToast({
title: "提交成功",
icon: "none"
})
formRef.value.resetFields()
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
</script>

View File

@ -0,0 +1,119 @@
<template>
<view class="px-base" v-if="detail">
<CuNavbar title="请假详情">
<template v-if="!isEdit" #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx">
<view class="py-20rpx flex items-center justify-between">
<view>申请人</view>
<view class="text-hex-999999">{{ detail.employee.name }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">{{ detail.store.title }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">{{ detail.employee.phone }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>申请时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.created_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>请假类型</view>
<view class="text-hex-999999">{{ detail.type.name }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>请假开始时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.start_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>请假结束时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.end_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>请假原因</view>
<view class="text-hex-999999 mt-20rpx">{{ detail.reason }}</view>
</view>
</view>
<view class="h-100rpx">
<view class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx">
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block> 拒绝 </uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-modal ref="modalRef" title="提示" content="确定删除吗?" @confirm="onSubmit" :showCancelButton="true"></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from "@/components/cu-navbar/index"
import { http } from "@/utils/request"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const modalRef = ref(null)
const columns = [["修改", "删除"]]
const detail = ref()
const pickerRef = ref(null)
const id = ref(0)
const open = () => {
pickerRef.value.open()
}
const confirmPicker = e => {
console.log(e)
if (e.value[0] === "删除") {
modalRef.value.open()
} else {
uni.navigateTo({
url: `/pages/ask-leave/create?id=${id.value}`
})
}
}
const onSubmit = async () => {
try {
await http.request({
url: `/hr/holidays/${id.value}`,
method: "DELETE",
header: {
Accept: "application/json"
}
})
uni.showToast({
title: "删除成功",
icon: "none"
})
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
onLoad(options => {
id.value = options.id
http
.request({
url: `/hr/holidays/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
detail.value = res
})
})
</script>

View File

@ -0,0 +1,91 @@
<template>
<view>
<CuNavbar title="请假申请">
<template #right>
<view
@click="goPath('/pages/ask-leave/create')"
class="text-24rpx text-white"
>申请</view
>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<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>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].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>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import Item from './components/item.vue'
const tabList = ref([
{
name: '我的请假',
apiUrl: '/hr/holidays',
},
{
name: '请假审核',
apiUrl: '/workflow',
params: {
subject_type: 'holiday_applies',
},
},
])
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,36 @@
<template>
<view class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx" @click="goPath">
<view class="text-30rpx">出差报备</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-120rpx text-right mr-20rpx">目的地</view>
<view class="">{{ item.address }}</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">出差事由</view>
<view class="">{{ item.reason }}</view>
</view>
<view class="flex items-center text-hex-999999 flex">
<view class="text-24rpx w-140rpx"> 申请时间</view>
<view class="text-24rpx">{{ timeFormat(item.created_at, "yyyy-mm-dd hh:MM:ss") }}</view>
</view>
<view
:style="{
color: statusFun(item.workflow_check.check_status, 'workflow_check', 'color')
}"
class="text-24rpx"
>{{ statusFun(item.workflow_check.check_status, "workflow_check", "name") }}</view
>
</view>
</template>
<script setup>
import statusFun from "@/utils/status"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const props = defineProps({
item: Object
})
const goPath = () => {
uni.navigateTo({
url: `/pages/business/detail?id=${props.item.id}`
})
}
</script>

View File

@ -0,0 +1,174 @@
<template>
<view>
<CuNavbar title="出差申请">
<template #right>
<view class="text-24rpx text-white" @click="submit"></view>
</template>
</CuNavbar>
<view class="card-shadow px-base">
<uv-form labelPosition="left" :model="form" :rules="rules" ref="formRef" errorType="toast" labelWidth="250rpx">
<uv-form-item required label="目的地" prop="address">
<uv-input placeholder="请选择" @click="openPicker" inputAlign="right" :border="`none`" v-model="form.address">
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="出差开始时间" prop="start_at">
<uv-input
placeholder="请选择日期"
readonly
@click="openStartDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.start_at"
>
</uv-input>
</uv-form-item>
<uv-form-item required label="出差结束时间" prop="end_at">
<uv-input
placeholder="请选择日期"
readonly
@click="openEndDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.end_at"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="出差事由" prop="reason" labelPosition="top">
<uv-textarea v-model="form.reason" count placeholder="请输入" :border="`none`" :maxlength="200"></uv-textarea>
</uv-form-item>
</uv-form>
</view>
<uv-datetime-picker
placeholder="请选择日期"
v-model="startValue"
ref="dateStartPicker"
mode="datetime"
@confirm="confirmStartDatePicker"
>
</uv-datetime-picker>
<uv-datetime-picker
v-model="endValue"
placeholder="请选择日期"
ref="dateEndPicker"
mode="datetime"
@confirm="confirmEndDatePicker"
>
</uv-datetime-picker>
<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"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const columns = ref([])
const pickerData = ref([])
const formRef = ref(null)
const dateStartPicker = ref(null)
const dateEndPicker = ref(null)
const modalRef = ref(null)
const startValue = ref(Number(new Date()))
const endValue = ref(Number(new Date()))
const id = ref(0)
const loading = ref(false)
const form = reactive({
start_at: "",
end_at: "",
reason: "",
address: ""
})
const openStartDatePicker = () => {
dateStartPicker.value.open()
}
const openEndDatePicker = () => {
dateEndPicker.value.open()
}
const confirmStartDatePicker = e => {
form.start_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const confirmEndDatePicker = e => {
form.end_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const rules = reactive({
start_at: [{ required: true, message: "请选择时间" }],
end_at: [{ required: true, message: "请选择时间" }],
reason: [{ required: true, message: "请输入出差理由" }],
address: [{ required: true, message: "请输入目的地" }]
})
onLoad(options => {
http
.request({
url: `/keywords?parent_key=holiday_type`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
let names = res.map(item => item.name)
columns.value = [names]
pickerData.value = res
})
id.value = options.id
if (id.value) {
http
.request({
url: `/hr/offical-bussiness/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
startValue.value = res.start_at * 1000
endValue.value = res.end_at * 1000
form.start_at = timeFormat(res.start_at, "yyyy-mm-dd hh:MM:ss")
form.end_at = timeFormat(res.end_at, "yyyy-mm-dd hh:MM:ss")
form.reason = res.reason
form.address = res.address
})
}
})
const submit = () => {
formRef.value.validate().then(res => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
let url = id.value ? `/hr/offical-bussiness/${id.value}` : "/hr/offical-bussiness"
let method = id.value ? "PUT" : "POST"
await http.request({
url: url,
method: method,
header: {
Accept: "application/json"
},
data: {
start_at: form.start_at,
address: form.address,
reason: form.reason,
end_at: form.end_at
}
})
uni.showToast({
title: "提交成功",
icon: "none"
})
formRef.value.resetFields()
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
</script>

View File

@ -0,0 +1,119 @@
<template>
<view class="px-base" v-if="detail">
<CuNavbar title="出差详情">
<template v-if="!isEdit" #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx">
<view class="py-20rpx flex items-center justify-between">
<view>申请人</view>
<view class="text-hex-999999">{{ detail.employee.name }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">{{ detail.store.title }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">{{ detail.employee.phone }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>申请时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.created_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>目的地</view>
<view class="text-hex-999999">{{ detail.address }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>出差开始时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.start_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>出差结束时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.end_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>出差原因</view>
<view class="text-hex-999999 mt-20rpx">{{ detail.reason }}</view>
</view>
</view>
<view class="h-100rpx">
<view class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx">
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block> 拒绝 </uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-modal ref="modalRef" title="提示" content="确定删除吗?" @confirm="onSubmit" :showCancelButton="true"></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from "@/components/cu-navbar/index"
import { http } from "@/utils/request"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const modalRef = ref(null)
const columns = [["修改", "删除"]]
const detail = ref()
const pickerRef = ref(null)
const id = ref(0)
const open = () => {
pickerRef.value.open()
}
const confirmPicker = e => {
console.log(e)
if (e.value[0] === "删除") {
modalRef.value.open()
} else {
uni.navigateTo({
url: `/pages/business/create?id=${id.value}`
})
}
}
const onSubmit = async () => {
try {
await http.request({
url: `/hr/offical-bussiness/${id.value}`,
method: "DELETE",
header: {
Accept: "application/json"
}
})
uni.showToast({
title: "删除成功",
icon: "none"
})
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
onLoad(options => {
id.value = options.id
http
.request({
url: `/hr/offical-bussiness/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
detail.value = res
})
})
</script>

View File

@ -0,0 +1,93 @@
<template>
<view>
<CuNavbar title="出差申请">
<template #right>
<view
@click="goPath('/pages/business/create')"
class="text-24rpx text-white"
>申请</view
>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<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>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
:params="tabList[1].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>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import Item from './components/item.vue'
const tabList = ref([
{
name: '我的请假',
apiUrl: '/hr/offical-bussiness',
},
{
name: '请假审核',
apiUrl: '/workflow',
params: {
subject_type: 'holiday_applies',
},
},
])
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

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

View File

@ -0,0 +1,43 @@
<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>
<uv-scroll-list v-if="item.images" :indicator="false">
<view class="space-x-15rpx flex">
<view v-for="(item, index) in item?.images ?? []" :key="index">
<image
:src="item + ''"
mode="heightFix"
style="height: 160rpx"
></image>
</view>
</view>
</uv-scroll-list>
<view
:style="{
color: statusFun(item.workflow_check.check_status, 'workflow_check', 'color'),
}"
class="text-24rpx"
>{{
statusFun(item.workflow_check.check_status, 'workflow_check', 'name')
}}</view
>
</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 = () => {
uni.navigateTo({
url: `/pages/contract/detail?id=${props.item.id}`,
})
}
</script>

View File

@ -0,0 +1,153 @@
<template>
<view class="">
<CuNavbar title="合同上传">
<template #right>
<view class="text-24rpx text-white" @click="submit"></view>
</template>
</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="name">
<uv-input placeholder="请输入合同名称" inputAlign="right" :border="`none`" v-model="form.name"> </uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="合同照片" labelPosition="top" prop="images" required>
<view class="w-full mt-15rpx">
<uv-upload
:maxCount="9"
multiple
:fileList="form.images"
@afterRead="afterRead"
@delete="deletePic"
name="images"
></uv-upload>
</view>
</uv-form-item>
</uv-form>
</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({
name: "",
images: []
})
const rules = reactive({
name: [{ required: true, message: "请输入清洁范围" }],
images: {
type: "array",
required: true,
message: "请上传报销凭证"
}
})
onLoad(options => {
id.value = options.id
if (id.value) {
http
.request({
url: `/agreements/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
form.name = res.name
form.images = res.images
})
}
})
const submit = () => {
formRef.value.validate().then(res => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
let url = id.value ? `/agreements/${id.value}` : "/agreements"
let method = id.value ? "PUT" : "POST"
await http.request({
url: url,
method: method,
header: {
Accept: "application/json"
},
data: {
name: form.name,
images: form.images.map(item => item.url)
}
})
uni.showToast({
title: "提交成功",
icon: "none"
})
formRef.value.resetFields()
uni.$emit("task:submit", resData)
uni.navigateBack()
} catch (error) {
console.log(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,137 @@
<template>
<view class="px-base" v-if="detail">
<CuNavbar title="合同详情">
<template #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view
class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx"
>
<view class="py-20rpx flex items-center justify-between">
<view>上传人</view>
<view class="text-hex-999999">{{ detail.employee.name }}</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<!-- <view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">{{ detail.store.title }}</view>
</view> -->
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">{{ detail.employee.phone }}</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>上传时间</view>
<view class="text-hex-999999">{{
timeFormat(detail.created_at, 'yyyy-mm-dd hh:MM')
}}</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>合同名称</view>
<view class="text-hex-999999">{{ detail.name }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>合同内容</view>
</view>
<uv-scroll-list :indicator="false">
<view class="space-x-15rpx flex">
<view v-for="(item, index) in detail.images" :key="index">
<image
:src="item"
mode="heightFix"
style="height: 160rpx"
></image>
</view>
</view>
</uv-scroll-list>
</view>
<view class="h-100rpx">
<view
class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx"
>
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block>
拒绝
</uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
<uv-picker
ref="pickerRef"
:columns="columns"
@confirm="confirmPicker"
></uv-picker>
<uv-modal
ref="modalRef"
title="提示"
content="确定删除吗?"
@confirm="onSubmit"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
const modalRef = ref(null)
const columns = [['修改', '删除']]
const detail = ref()
const pickerRef = ref(null)
const id = ref(0)
const open = () => {
pickerRef.value.open()
}
const confirmPicker = (e) => {
console.log(e)
if (e.value[0] === '删除') {
modalRef.value.open()
} else {
uni.navigateTo({
url: `/pages/contract/create?id=${id.value}`,
})
}
}
const onSubmit = async () => {
try {
await http.request({
url: `/agreements/${id.value}`,
method: 'DELETE',
header: {
Accept: 'application/json',
},
})
uni.showToast({
title: '删除成功',
icon: 'none',
})
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
onLoad((options) => {
id.value = options.id
http
.request({
url: `/agreements/${options.id}`,
method: 'GET',
header: {
Accept: 'application/json',
},
})
.then((res) => {
detail.value = res
})
})
</script>

View File

@ -0,0 +1,97 @@
<template>
<view>
<CuNavbar title="我的合同">
<template #right>
<view
@click="goPath('/pages/contract/create')"
class="text-24rpx text-white"
>上传</view
>
</template>
</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>
<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>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
:params="tabList[1].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>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
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: '/agreements',
},
{
name: '合同审核',
apiUrl: '/workflow',
params: {
subject_type: 'agreements',
},
},
])
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<view class="card">
<view class="flex justify-between">
<view class="font-600">{{ data.month }}</view>
<view class="space-x-20rpx">
<text>提成</text>
<text class="text-primary">{{ data.commission }}</text>
</view>
</view>
<view class="flex mt-20rpx">
<view class="w-160rpx">支出</view>
<view class="flex-1 grid grid-cols-3 text-gray-500">
<view class="text-right">
<text>日常</text>
<text>{{ data.daily_expenses }}</text>
</view>
<view class="text-right">
<text>员工</text>
<text>{{ data.employee_expenses }}</text>
</view>
<view class="text-right">
<text>其他</text>
<text>{{ data.other_expenses }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
data: {
type: Object,
default: () => {},
},
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,50 @@
<template>
<view>
<CuNavbar title="提成数据" isBack></CuNavbar>
<mescroll-body @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="space-y-20rpx px-base mt-20rpx">
<view v-for="(item, i) in list" :key="i">
<Item :data="item"></Item>
</view>
</view>
</mescroll-body>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import Item from './components/item.vue'
import { onLoad } from '@dcloudio/uni-app'
import { http } from '@/utils/request'
import { 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 upCallback = async (mescroll) => {
const { size, num } = mescroll
try {
const resData = await http.get('/account/store-master-commissions', {
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) {
mescroll.endErr() // ,
}
}
</script>

View File

@ -0,0 +1,128 @@
<template>
<view>
<CuNavbar title="业绩数据" isBack></CuNavbar>
<view class="bg-primary bg-opacity-80 p-base">
<DateTime v-model="currentDate" @confirm="dateConfirm" />
<view class="flex items-center py-base text-white">
<view class="flex-1 text-center">
<view class="text-32rpx">当前业绩</view>
<view class="mt-10rpx">{{ countData.actual_performance }}</view>
</view>
<view>/</view>
<view class="flex-1 text-center">
<view class="text-32rpx">目标业绩</view>
<view class="mt-10rpx">{{ countData.expected_performance }}</view>
</view>
</view>
</view>
<uv-sticky bgColor="#fff" zIndex="20">
<view class="">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:lineColor="'#ee2c37'"
:scrollable="false"
:list="tabList"
:current="currentTab"
@change="tabChange"
></uv-tabs>
</view>
</uv-sticky>
<view class="">
<view v-for="(ob, i) in showList" :key="i">
<uv-sticky bgColor="#f5f5f5" zIndex="10" offsetTop="44">
<view class="h-80rpx flex items-center px-base">
<TitleComp :title="`${timeFormat(i, 'yyyy')}`"></TitleComp>
</view>
</uv-sticky>
<view class="card">
<view v-for="(item, i) in ob" :key="i">
<view class="flex items-center h-84rpx">
<view class="w-110rpx text-primary"
>{{ timeFormat(i, 'mm') }}</view
>
<view class="flex-1"
>{{ item.actual_performance }}/{{
item.expected_performance
}}</view
>
<view
:style="{
color: statusFun(item.status, 'shore_task_status', 'color'),
}"
>{{ statusFun(item.status, 'shore_task_status', 'name') }}</view
>
</view>
<uv-line v-if="i !== ob.length - 1" color="#f5f5f5"></uv-line>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import TitleComp from '@/components/title-comp/index'
import DateTime from '@/components/date-time/index'
import { http } from '@/utils/request'
import { onLoad } from '@dcloudio/uni-app'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
import { ref, reactive, computed } from 'vue'
import statusFun from '@/utils/status'
const list = ref([])
const tabList = [
{ name: '业绩目标', value: 'future' },
{ name: '完成业绩', value: 'history' },
]
const currentTab = ref(0)
const historyDate = uni.getStorageSync('historyDate')
const currentDate = ref(historyDate || Number(new Date()))
const countData = reactive({
actual_performance: 0,
expected_performance: 0,
})
const currentTabObj = computed(() => tabList[currentTab.value])
const showList = computed(() => encapsulateDataByMonth(list.value))
onLoad(() => {
getCount()
getList()
})
const dateConfirm = (e) => {
uni.setStorageSync('historyDate', e.value)
currentDate.value = e.value
getCount()
}
const getCount = async () => {
const resData = await http.get('/account/store-performance', {
params: {
month: timeFormat(currentDate.value, 'yyyy-mm'),
},
})
Object.assign(countData, resData)
}
const tabChange = (e) => {
currentTab.value = e.index
getList()
}
const getList = async () => {
const resData = await http.get('/account/store-performance-tasks', {
params: {
filter: currentTabObj.value.value,
},
})
list.value = resData
}
function encapsulateDataByMonth(data) {
return data.reduce((result, item) => {
const { month, ...rest } = item
result[month] = result[month] || []
result[month].push(rest)
return result
}, {})
}
</script>

View File

@ -0,0 +1,108 @@
<template>
<view class="px-base">
<CuNavbar title="数据上报"></CuNavbar>
<view class="mt-30rpx">
<uv-form
labelWidth="140rpx"
labelPosition="left"
:borderBottom="false"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
>
<view class="space-y-15rpx">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form-item label="日期">
<view class="w-full">
{{ form.date }}
</view>
</uv-form-item>
</view>
<view> </view>
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form-item>
<TitleComp title="电彩" textSize="32rpx"></TitleComp>
</uv-form-item>
<uv-form-item label="销售" borderBottom>
<uv-input
border="none"
placeholder="请输入电彩销售金额"
></uv-input>
</uv-form-item>
<uv-form-item label="兑奖">
<uv-input
border="none"
placeholder="请输入电彩兑奖金额"
></uv-input>
</uv-form-item>
</view>
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form-item>
<TitleComp title="汇总情况" textSize="32rpx"></TitleComp>
</uv-form-item>
<uv-form-item label="销售合计" borderBottom>
<uv-input
border="none"
placeholder="请输入总帐销售金额"
></uv-input>
</uv-form-item>
<uv-form-item label="兑奖合计" borderBottom>
<uv-input
border="none"
placeholder="请输入总帐兑奖金额"
></uv-input>
</uv-form-item>
<uv-form-item label="新增客户" borderBottom>
<uv-input
border="none"
placeholder="请输入微信新增人数"
></uv-input>
</uv-form-item>
<uv-form-item label="交账金额" borderBottom>
<uv-input border="none" placeholder="请输入交账金额"></uv-input>
</uv-form-item>
<view class="text-primary text-xs mt-10rpx pb-base"
>*请确保填写的交账金额正确无误</view
>
</view>
<view class="card-shadow bg-white rounded-19rpx px-base pb-base">
<uv-form-item>
<view class="w-full">
<TitleComp title="时段报表照片" textSize="32rpx">
<template #right>
<view class="text-hex-999999"> 0/9 </view>
</template>
</TitleComp>
</view>
</uv-form-item>
<uv-form-item>
<uv-upload></uv-upload>
</uv-form-item>
<view class="text-primary text-xs">
*竞彩时段报表照片玩法时段报表照片每日账本上传需亲笔签字销量本上传
</view>
</view>
</view>
</uv-form>
</view>
<uv-datetime-picker ref="datetimePicker" v-model="form.date" mode="date">
</uv-datetime-picker>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import TitleComp from '@/components/title-comp/index'
import { ref, reactive } from 'vue'
const formRef = ref(null)
const datetimePicker = ref(null)
const form = reactive({
date: '2022-01-01',
})
const rules = reactive({})
</script>

View File

@ -0,0 +1,168 @@
<template>
<view>
<CuNavbar title="培训考试">
<template v-if="!readonly" #right>
<view class="text-white" @click="submit"></view>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
<view class="flex items-center justify-between h-90rpx px-base">
<view class="w-140rpx">
<view class="btn" :disabled="index == 0" @click="prev"></view>
</view>
<view>{{ index + 1 }}/{{ total }}</view>
<view class="w-140rpx text-right">
<view class="btn" :disabled="index == total - 1" @click="next">
下一题</view
>
</view>
</view>
</uv-sticky>
<view class="p-base">
<view class="card-shadow bg-white rounded-19rpx p-base min-h-30vh">
<template v-for="(item, key) in list" :key="key">
<view v-show="key == index" class="item">
<view class="title text-30rpx"
>({{ item.cate_name }}){{ item.title }}
<text v-if="readonly" class="text-primary"
>(得分{{ item.user_score }})</text
>
</view>
<view
class="pt-base"
:class="[readonly ? 'pointer-events-none' : '']"
>
<template v-if="item.cate == 2">
<uv-checkbox-group
activeColor="#ee2c37"
v-model="item.answer"
placement="column"
>
<uv-checkbox
:customStyle="{ margin: '8px' }"
v-for="(op, i) in item.options"
:key="i"
:label="op.text"
:name="op.text"
>
</uv-checkbox>
</uv-checkbox-group>
</template>
<template v-if="item.cate == 1">
<uv-radio-group
activeColor="#ee2c37"
v-model="item.answer"
placement="column"
>
<uv-radio
:customStyle="{ margin: '8px' }"
v-for="(op, i) in item.options"
:key="i"
:label="op.text"
:name="op.text"
>
</uv-radio>
</uv-radio-group>
</template>
</view>
</view>
</template>
</view>
</view>
<uv-modal
ref="modalRef"
title="提示"
:content="`是否确认提交?`"
@confirm="onSubmit"
></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { onLoad } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
const modalRef = ref(null)
const info = ref({})
const id = ref(0)
//
// const list = ref([])
//
// const total = ref(0)
//
const index = ref(0)
//
// const readonly = ref(true)
const loading = ref(false)
const list = computed(() => {
const content = info.value?.content ?? []
content.forEach((item) => {
if (item.score == 1) {
item.answer = item.user_answer[0] ?? ''
} else {
item.answer = item.user_answer ?? []
}
})
return info.value.content || []
})
const total = computed(() => list.value.length)
const readonly = computed(() => (info.value.finished_at ? true : false))
const answer = computed(() => {
const arr = list.value.reduce((a, b) => {
const c = [].concat(b.answer ?? [])
a.push(c)
return a
}, [])
return arr || []
})
onLoad((options) => {
id.value = options.id
http.get(`/train/examinations/${options.id}`).then((resData) => {
const res = resData
info.value = res
})
})
const next = () => {
if (index.value < total.value - 1) {
index.value++
}
}
const prev = () => {
if (index.value > 0) {
index.value--
}
}
const submit = () => {
modalRef.value.open()
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
await http.post(`/train/examinations/${id.value}/answer`, {
answers: answer.value,
})
uni.$emit('examination:onRefresh')
uni.navigateBack()
} catch (error) {
} finally {
loading.value = false
}
}
</script>
<style scoped lang="scss">
.btn {
@apply text-28rpx;
}
.btn[disabled='true'] {
color: #999;
@apply pointer-events-none;
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<view>
<CuNavbar title="培训考试"></CuNavbar>
<MescrollItem :top="88" :i="0" apiUrl="/train/examinations">
<template v-slot="{ list }">
<view class="space-y-15rpx p-base">
<template v-for="item in list" :key="item.id">
<view
class="card-shadow space-y-10rpx bg-white rounded-19rpx p-base space-y-10rpx"
@click="detail(item)"
>
<view class="text-30rpx">{{ item.examination.name }}</view>
<view
class="flex items-center justify-between text-24rpx text-hex-999999"
>
<view class=""
>发布日期:
{{ timeFormat(item.examination.published_at) }}</view
>
<view class="">{{
item.mark != null ? item.mark : '未完成'
}}</view>
</view>
</view>
</template>
</view>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { http } from '@/utils/request'
import CuNavbar from '@/components/cu-navbar/index'
import MescrollItem from '@/components/mescroll-api/one'
import { onPageScroll, onReachBottom, onLoad } from '@dcloudio/uni-app'
import useMescrollComp from '@/uni_modules/mescroll-uni/hooks/useMescrollComp.js'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
const { mescrollItem } = useMescrollComp(onPageScroll, onReachBottom)
onLoad(() => {
uni.$on('examination:onRefresh', () => {
mescrollItem.value?.refresh()
})
})
const detail = (item) => {
uni.navigateTo({
url: `/pages/examination/detail?id=${item.id}`,
})
}
</script>

View File

@ -0,0 +1,113 @@
<template>
<view>
<CuNavbar title="报销管理">
<template #right>
<view
@click="goPath('/pages/expense-account/submit')"
class="text-24rpx text-white"
>申请</view
>
</template>
</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 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>
</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: '我的报销',
},
{
name: '报销审核',
},
])
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,
})
}
</script>

View File

@ -0,0 +1,214 @@
<template>
<view class="px-base">
<CuNavbar title="报销申请"> </CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form
labelWidth="160rpx"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
labelPosition="left"
>
<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-line color="#f5f5f5"></uv-line>
<uv-form-item label="报销金额" required prop="expense">
<uv-input
:border="`none`"
placeholder="请输入报销金额"
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="reason"
:borderBottom="true"
labelPosition="top"
>
<uv-textarea
:border="`none`"
v-model="form.reason"
placeholder="请输入报销原因"
></uv-textarea>
</uv-form-item>
<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>
<view class="mt-100rpx">
<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="onSubmit"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
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({
reimbursement_type_id: '',
expense: '',
reason: '',
photos: [],
})
const rules = reactive({
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 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

@ -0,0 +1,45 @@
<template>
<view>
<qiun-data-charts type="area" :opts="opts" :chartData="chartData" />
</view>
</template>
<script setup>
import { ref } from 'vue'
const opts = ref({
color: [
'#ee2c37',
],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: {
show:false
},
xAxis: {
disableGrid: true,
},
yAxis: {
gridType: 'dash',
dashLength: 2,
},
extra: {
area: {
type: 'curve',
opacity: 0.2,
addLine: true,
width: 2,
gradient: true,
activeType: 'hollow',
},
},
})
const chartData = ref({
categories: ['2016', '2017', '2018', '2019', '2020', '2021'],
series: [
{
name: '目标值',
data: [35, 36, 31, 33, 13, 34],
}
],
})
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,193 @@
<template>
<view>
<uv-sticky bgColor="#fff">
<view class="flex-center h-44px">
<view class="flex-center flex-1" @click="selectMenu({ name: 'shore' })">
<view>全部区域</view>
<uv-icon
class="ml-10rpx"
size="20rpx"
name="arrow-down-fill"
></uv-icon>
</view>
<view class="flex-center flex-1" @click="selectMenu({ name: 'store' })">
<view>全部区域</view>
<uv-icon
class="ml-10rpx"
size="20rpx"
name="arrow-down-fill"
></uv-icon>
</view>
</view>
</uv-sticky>
<uv-picker
ref="shoreRef"
keyName="name"
@change="shoreChange"
:columns="shoreList"
@confirm="shoreConfirm"
></uv-picker>
<uv-picker
ref="storeRef"
keyName="address"
@change="shoreChange"
:columns="storeList"
@confirm="shoreConfirm"
></uv-picker>
</view>
</template>
<script>
import { http } from '@/utils/request'
import data from './da.json'
export default {
onPageScroll() {
//
// this.$refs.dropDown.init()
},
computed: {
dropItem(name) {
return (name) => {
return {
label: name,
value: name,
}
}
},
//
currentDropItem() {
return this[this.activeName]
},
},
data() {
return {
shoreList: [],
storeList: [],
// value
defaultValue: [0, 'all'],
//
result: [],
activeName: 'shore',
shore: {
label: '全部区域',
activeIndex: [0, 0],
color: '#333',
activeColor: '#2878ff',
},
store: {
label: '全部门店',
activeIndex: 0,
color: '#333',
activeColor: '#2878ff',
},
cityData: data,
}
},
created() {
this.init()
},
methods: {
async init() {
const province = this.cityData.province
this.shoreList = [
province,
this.getCityByProvince(province[this.shore.activeIndex[1]].code),
]
},
change(e) {
console.log('弹窗打开状态:', e)
},
/**
* 点击每个筛选项回调
* @param {Object} e { name, active, type } = e
*/
selectMenu(e) {
const { name } = e
this.activeName = name
if (name === 'shore') {
const active = this.shore.activeIndex
const list = this.getCityByProvince(this.shoreList[0][active[0]].code)
this.shoreList[1] = list
this.$refs.shoreRef.setColumnValues(1, list)
this.$refs.shoreRef.setIndexs(active, true)
this.$refs.shoreRef.open()
}
if (name === 'store') {
this.$refs.storeRef.open()
}
},
/**
* 点击菜单回调处理
* @param {Object} item 选中项 { label,value } = e
*/
clickItem(e) {},
shoreChange(e) {
const { columnIndex, index } = e
if (columnIndex == 0) {
const item = this.shoreList[columnIndex][index]
this.shoreList[1] = this.getCityByProvince(item.code)
this.$refs.shoreRef.setColumnValues(
1,
this.getCityByProvince(item.code)
)
}
},
shoreConfirm(e) {
this.shore.activeIndex = e.indexs
const item = {
name: 'shore',
}
const cityIndex = this.shore.activeIndex[1]
const index = cityIndex == 0 ? 0 : 1
const index2 = e.indexs[index]
},
getCityByProvince(province) {
return [
{
name: '全部',
code: 'all',
},
].concat(this.cityData.city[province])
},
getStoreByCity(city) {
const res = http.get('/auth/stores', {
params: {
city: city,
},
})
this.storeList = [
{
id: 1,
title: '1',
master_id: 1,
category_id: 'store_category_1_1',
business_id: 'store_business_1',
level_id: 'store_level_2',
region: {
city: '天津市市辖区',
code: 120100,
street: null,
cityCode: 120100,
district: null,
province: '天津市',
districtCode: 0,
provinceCode: 120000,
},
address: '回龙观(地铁站)',
lon: '116.34266369754',
lat: '40.076418413591',
profit_ratio: 0,
profit_money: '0.00',
business_status: 1,
created_at: '2024-04-03 17:17:02',
updated_at: '2024-04-03 17:17:02',
business_status_text: '开业',
business_status_color: 'success',
},
]
},
},
}
</script>

View File

@ -0,0 +1,59 @@
<template>
<view>
<view>
<StoreDropDown></StoreDropDown>
</view>
<view class="bg-primary p-base text-center text-white relative">
<view class="absolute top-20rpx right-20rpx">
<uv-icon color="#fff" size="48rpx" name="chat"></uv-icon>
</view>
<view class="mt-60rpx">昨日累计金额</view>
<view class="mt-20rpx">截止2024-03-21</view>
<view class="flex items-center mt-40rpx">
<view class="flex-1 text-center">
<view>销售</view>
<view>20000</view>
</view>
<view class="h-80rpx flex-none flex-center">
<uv-line direction="vertical"></uv-line>
</view>
<view class="flex-1 text-center">
<view>支出</view>
<view>20000</view>
</view>
</view>
</view>
<view>
<view class="h-80rpx leading-80rpx px-base">近30天趋势数据</view>
</view>
<view>
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
lineColor="#ee2c37"
:list="list"
@click="onTabClick"
:scrollable="false"
></uv-tabs>
<ChartComp></ChartComp>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import ChartComp from './components/chart.vue'
import StoreDropDown from '@/pages/home/components/store-drop-down/index.vue'
const list = ref([
{
name: '销售金额',
},
{
name: '支出金额',
},
])
const onTabClick = (e) => {
console.log(e)
}
</script>

View File

@ -0,0 +1,50 @@
<template>
<view class="content">
<uv-icon name="photo" size="30" color="#909399"></uv-icon>
<uv-button type="primary" text="确定">我是按钮</uv-button>
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{ title }}</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello',
}
},
onLoad() {},
methods: {},
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<view class="w-full flex flex-col">
<view class="mt-20vh">
<view class="b-1px b-solid h-120rpx"></view>
</view>
<view class="text-35rpx text-hex-333333 font-600 mt-80rpx">体彩管理系统</view>
<view class="text-27rpx text-hex-333333">欢迎登录</view>
<view class="flex-1 flex flex-col justify-end mt-0rpx">
<LoginForm></LoginForm>
</view>
</view>
</template>
<script setup>
import LoginForm from './LoginForm.vue'
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,65 @@
<template>
<view class="">
<uv-form>
<uv-form-item>
<view class="h-92rpx bg-white rounded-full w-full flex-center">
<uv-input
v-model="form.username"
shape="circle"
maxlength="11"
placeholder="请输入帐号"
type="text"
border="none1"
fontSize="27rpx"
>
</uv-input>
</view>
</uv-form-item>
<uv-form-item>
<view class="h-92rpx bg-white rounded-full w-full flex-center">
<uv-input
v-model="form.password"
shape="circle"
placeholder="请输入密码"
type="password"
border="none1"
fontSize="27rpx"
>
</uv-input>
</view>
</uv-form-item>
</uv-form>
<view class="mt-115rpx">
<uv-button block type="primary" shape="circle" @click="handleClick"
>登录</uv-button
>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const form = ref({
username: '',
password: '',
})
const handleClick = async () => {
try {
// const { username, password } = form.value
// await userStore.login({
// username,
// password,
// })
uni.switchTab({
url: '/pages/home/index',
})
} catch (e) {
console.log(e)
}
}
</script>

View File

@ -0,0 +1,53 @@
<template>
<view>
<view class="mt-115rpx">
<uv-button block type="primary_btn1" shape="circle" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber">微信一键登录
</uv-button>
<view class="mt-31rpx">
<cu-checkbox v-model="checkbox">
<view class="flex items-center flex-wrap text-23rpx leading-33rpx text-hex-333333">
<view class="text-hex-999999 float-left">
登录即代表同意
</view>
<text>用户协议隐私政策</text>
<text class="text-hex-999999">
</text>
<text>第三方SDK类服务商说明</text>
</view>
</cu-checkbox>
</view>
</view>
</view>
</template>
<script setup>
import CuCheckbox from "@/components/CuCheckbox/cu-checkbox.vue";
import {ref} from 'vue'
import {useUserStore} from "@/store/modules/user";
const userStore = useUserStore()
const checkbox = ref(false)
const getPhoneNumber = (e) => {
const {appId, version} = uni.getAccountInfoSync().miniProgram
userStore.wchatLogin({
appId,
version,
type: 'getPhoneNumber',
code: e.code
})
uni.switchTab({
url: '/pages/tabbar/home',
})
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,42 @@
<template>
<Cupopup mode="bottom" v-model:visible="ruleShow" title="请阅读并同意以下条款" @cancel="handleClick('cancel')"
@confirm="handleClick('confirm')">
<view @click="ruleClick">
登录即代表同意用户协议隐私政策 登录即代表同意用户协议隐私政策第三方SDK类服务商说明
</view>
</Cupopup>
</template>
<script setup>
import Cupopup from "@/components/CuPopup/index.vue";
import {ref, watch, watchEffect} from "vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
}
})
const ruleShow = ref(props.visible)
const emit = defineEmits(['update:visible', 'onClick','ruleClick'])
const handleClick = (e) => {
emit('onClick', e)
}
const ruleClick = () => {
emit('ruleClick')
}
watchEffect(() => {
ruleShow.value = props.visible
})
watch(() => ruleShow.value, (val) => {
emit('update:visible', val)
})
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,53 @@
<template>
<Cupopup mode="bottom" v-model:visible="ruleShow" title="用户服务协议及隐私政策" @cancel="handleClick('cancel')" @confirm="handleClick('confirm')">
<view class="space-y-20rpx">
<view class="indent-46rpx">
小程序用户协议和隐私政策是小程序开发者为保障用户合法权益和保护用户隐私而制
</view>
<view>定的两个重要文件下面分别介绍一下小程序用户协议和隐私政策的内容和要点</view>
<view>用户协议 用户协议是小程序开发者与用户之间达成的协议 规定了用户使用小程序的
条件权利和义务等内容用户在使用小程序之前需要同意用户协议否则无法使用小程序
</view>
<view> 一般来说小程序用户协议包括以下几个方面的内容</view>
<view>1. 用户注册和账号管理规定包括用户注册账号安全和管理等规定</view>
<view>2. 使用规范和限制 包括用户在小程序上发布内容的规范禁止发布的内容和行为等规定</view>
<view>3. 用户权利和义务包括用户在使用小程序时享有的权利和应承担的义务等规定</view>
<view>4. 免责声明和责任限制包括小程序开发者对小程序服务的免责声明和责任限制等规定</view>
<view>5. 争议解决方式包括双方在发生争议时解决方式的规定</view>
<view>6. 隐私政策 隐私政策是小程序开发者为保护用户隐私而制定的政策 规定了小程序开发
者在收集使用存储保护用户个人信息时应遵守的原则和措施用户在使用小程序 需要同意隐私政策 否则无法使用小程序
一般来
</view>
</view>
</Cupopup>
</template>
<script setup>
import Cupopup from '@/components/CuPopup/index.vue'
import {ref, watch, watchEffect} from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false,
}
})
const ruleShow = ref(props.visible)
const emit = defineEmits(['update:visible', 'onClick'])
const handleClick = (e) => {
emit('onClick', e)
}
watchEffect(() => {
ruleShow.value = props.visible
})
watch(() => ruleShow.value, (val) => {
emit('update:visible', val)
})
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,11 @@
<template>
<view class="px-106rpx min-h-screen">
<Layout></Layout>
</view>
</template>
<script setup>
import Layout from './Layout.vue'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
</script>

View File

@ -0,0 +1,40 @@
<template>
<view class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx" @click="goPath">
<view class="text-30rpx">补卡申请</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">补卡原因</view>
<view class="">{{ item.reason }}</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">补卡类别</view>
<view class="">{{ item.sign_time == 1 ? "上班打卡" : "下班打卡" }}</view>
</view>
<view class="flex items-center text-hex-999999 flex">
<view class="text-24rpx w-140rpx"> 补卡时间</view>
<view class="text-24rpx">{{ valueFormat }}</view>
</view>
<view
:style="{
color: statusFun(item.workflow_check.check_status, 'workflow_check', 'color')
}"
class="text-24rpx"
>{{ statusFun(item.workflow_check.check_status, "workflow_check", "name") }}</view
>
</view>
</template>
<script setup>
import statusFun from "@/utils/status"
import { computed } from "vue"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const props = defineProps({
item: Object
})
const valueFormat = computed(() => {
return timeFormat(props.item.date, "yyyy-mm-dd hh:MM")
})
const goPath = () => {
uni.navigateTo({
url: `/pages/make-card/detail?id=${props.item.id}`
})
}
</script>

View File

@ -0,0 +1,165 @@
<template>
<view>
<CuNavbar title="补卡申请">
<template #right>
<view class="text-24rpx text-white" @click="submit"></view>
</template>
</CuNavbar>
<view class="card-shadow px-base">
<uv-form labelPosition="left" :model="form" :rules="rules" ref="formRef" errorType="toast" labelWidth="250rpx">
<uv-form-item required label="补卡时间" prop="date">
<uv-input
placeholder="请选择日期"
readonly
@click="openDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.date"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="补卡类型" prop="sign_time">
<uv-input
placeholder="请选择"
@click="openPicker"
readonly
inputAlign="right"
:border="`none`"
v-model="form.sign_time"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="补卡理由" prop="reason" labelPosition="top">
<uv-textarea v-model="form.reason" count placeholder="请输入" :border="`none`" :maxlength="200"></uv-textarea>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="外勤" prop="isOutSide">
<view class="flex flex-1 justify-end">
<uv-switch size="20" v-model="form.isOutSide"></uv-switch>
</view>
</uv-form-item>
<uv-form-item v-if="form.isOutSide" required label="外勤事由" prop="outside_remarks" labelPosition="top">
<uv-textarea
v-model="form.outside_remarks"
count
placeholder="请输入"
:border="`none`"
:maxlength="200"
></uv-textarea>
</uv-form-item>
</uv-form>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-datetime-picker
v-model="value"
placeholder="请选择日期"
ref="datetimePicker"
mode="datetime"
@confirm="confirmDatePicker"
>
</uv-datetime-picker>
<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"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const columns = [["上班补卡", "上班补卡"]]
const formRef = ref(null)
const datetimePicker = ref(null)
const pickerRef = ref(null)
const modalRef = ref(null)
const value = ref(Number(new Date()))
const id = ref(0)
const loading = ref(false)
const form = reactive({
date: "",
sign_time: "",
reason: "",
outside_remarks: "",
isOutSide: false
})
const openPicker = () => {
pickerRef.value.open()
}
const openDatePicker = () => {
datetimePicker.value.open()
}
const confirmDatePicker = e => {
form.date = timeFormat(e.value, "yyyy-mm-dd hh:MM")
}
const confirmPicker = e => {
form.sign_time = e.value[0]
}
const rules = reactive({
date: [{ required: true, message: "请选择时间" }],
sign_time: [{ required: true, message: "请选择类型" }],
reason: [{ required: true, message: "请输入补卡理由" }],
outside_remarks: [{ required: true, message: "请输入补卡理由" }]
})
onLoad(options => {
id.value = options.id
if (id.value) {
http
.request({
url: `/hr/sign-repairs/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
value.value = res.date * 1000
form.date = timeFormat(res.date, "yyyy-mm-dd hh:MM")
form.reason = res.reason
form.isOutSide = res.sign_type == 1 ? false : true
form.outside_remarks = res.outside_remarks
form.sign_time = res.sign_time == 1 ? "上班补卡" : "下班补卡"
})
}
})
const submit = () => {
formRef.value.validate().then(res => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
let url = id.value ? `/hr/sign-repairs/${id.value}` : "/hr/sign-repairs"
let method = id.value ? "PUT" : "POST"
await http.request({
url: url,
method: method,
header: {
Accept: "application/json"
},
data: {
date: form.date,
sign_time: form.sign_time == "上班补卡" ? 1 : 2,
reason: form.reason,
outside_remarks: form.outside_remarks,
sign_type: form.isOutSide ? 2 : 1
}
})
uni.showToast({
title: "提交成功",
icon: "none"
})
formRef.value.resetFields()
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<view class="px-base" v-if="detail">
<CuNavbar title="加班审核">
<template v-if="!isEdit" #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx">
<view class="py-20rpx flex items-center justify-between">
<view>申请人</view>
<view class="text-hex-999999">{{ detail.employee.name }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">{{ detail.store.title }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">{{ detail.employee.phone }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>申请时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.created_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>补卡时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.date, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>补卡原因</view>
<view class="text-hex-999999 mt-20rpx">{{ detail.reason }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>是否外勤</view>
<view class="text-hex-999999">{{ detail.sign_type == 1 ? "否" : "是" }}</view>
</view>
<template v-if="detail.sign_type == 2">
<uv-line></uv-line>
<view class="py-20rpx">
<view>外勤事由</view>
<view class="text-hex-999999 mt-20rpx">{{ detail.outside_remarks }}</view>
</view>
</template>
<uv-line></uv-line>
</view>
<view class="h-100rpx">
<view class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx">
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block> 拒绝 </uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-modal ref="modalRef" title="提示" content="确定删除吗?" @confirm="onSubmit" :showCancelButton="true"></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from "@/components/cu-navbar/index"
import { http } from "@/utils/request"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const modalRef = ref(null)
const columns = [["修改", "删除"]]
const detail = ref()
const pickerRef = ref(null)
const id = ref(0)
const open = () => {
pickerRef.value.open()
}
const confirmPicker = e => {
console.log(e)
if (e.value[0] === "删除") {
modalRef.value.open()
} else {
uni.navigateTo({
url: `/pages/make-card/create?id=${id.value}`
})
}
}
const onSubmit = async () => {
try {
await http.request({
url: `/hr/sign-repairs/${id.value}`,
method: "DELETE",
header: {
Accept: "application/json"
}
})
uni.showToast({
title: "删除成功",
icon: "none"
})
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
onLoad(options => {
id.value = options.id
http
.request({
url: `/hr/sign-repairs/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
detail.value = res
})
})
</script>

View File

@ -0,0 +1,92 @@
<template>
<view>
<CuNavbar title="补卡申请">
<template #right>
<view
@click="goPath('/pages/make-card/create')"
class="text-24rpx text-white"
>申请</view
>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<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>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
:params="tabList[1].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>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import Item from './components/item.vue'
const tabList = ref([
{
name: '我的补卡',
apiUrl: '/hr/sign-repairs',
},
{
name: '补卡审核',
apiUrl: '/workflow',
params: {
subject_type: 'employee_sign_repairs',
},
},
])
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,23 @@
<template>
<view class="flex-center flex-col" @click="onClick">
<uv-icon :name="data.icon" size="38" color="#909399"></uv-icon>
<view class="text-28rpx mt-10rpx">{{ data.title }}</view>
</view>
</template>
<script setup>
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
})
const onClick = () => {
if (props.data.url) {
uni.navigateTo({
url: props.data.url,
})
}
}
</script>

View File

@ -0,0 +1,116 @@
<template>
<view>
<CuNavbar :isBack="false" title="我的">
<template #right>
<view class="h-full flex-center">
<image
@click="goPath('/pages/setting/index')"
class="w-32rpx h-32rpx"
src="/static/images/setting.svg"
></image>
</view>
</template>
</CuNavbar>
<view class="px-base space-y-15rpx mt-30rpx">
<view class="card" v-for="(item, i) in opList" :key="i">
<TitleComp :title="item.title"></TitleComp>
<view class="grid grid-cols-3 mt-20rpx gap-20rpx">
<OpItem v-for="(child, ii) in item.children" :key="ii" :data="child"></OpItem>
</view>
</view>
</view>
</view>
</template>
<script setup>
import TitleComp from "@/components/title-comp/index"
import OpItem from "./components/op-item.vue"
import CuNavbar from "@/components/cu-navbar/index"
const opList = [
{
title: "门店数据",
children: [
{
icon: "order",
title: "业绩数据",
url: "/pages/data/performance/index"
},
{
icon: "folder",
title: "提成数据",
url: "/pages/data/brokerage/index"
}
]
},
{
title: "办公管理",
children: [
{
icon: "photo-fill",
title: "员工管理",
url: "/pages/user/index"
},
{
icon: "lock",
title: "我的任务",
url: "/pages/task/index"
},
{
icon: "home-fill",
title: "报销管理",
url: "/pages/expense-account/index"
},
{
icon: "map-fill",
title: "升职申请",
url: "/pages/work/list"
},
{
icon: "grid-fill",
title: "补卡申请",
url: "/pages/make-card/list"
},
{
icon: "car",
title: "请假申请",
url: "/pages/ask-leave/list"
},
{
icon: "setting-fill",
title: "出差报备",
url: "/pages/business/list"
},
{
icon: "server-man",
title: "加班报备",
url: "/pages/overtime/list"
},
{
icon: "camera",
title: "合同管理",
url: "/pages/contract/list"
}
]
},
{
title: "天天向上",
children: [
{
icon: "account",
title: "培训课件",
url: "/pages/train-books/index"
},
{
icon: "twitte",
title: "培训考试",
url: "/pages/examination/index"
}
]
}
]
const goPath = url => {
uni.navigateTo({
url
})
}
</script>

View File

@ -0,0 +1,39 @@
<template>
<view class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx" @click="goPath">
<view class="text-30rpx">加班报备</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">加班日期</view>
<view class="">{{ timeFormat(item.date, "yyyy-mm-dd") }}</view>
</view>
<view class="flex items-center text-hex-999999 flex">
<view class="text-24rpx w-140rpx"> 加班时间</view>
<view class="text-24rpx"
>{{ timeFormat(item.start_at, "yyyy-mm-dd hh:MM").substring(10) }} -
{{ timeFormat(item.end_at, "yyyy-mm-dd hh:MM").substring(10) }}</view
>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="text-24rpx w-140rpx">加班事由</view>
<view class="">{{ item.reason }}</view>
</view>
<view
:style="{
color: statusFun(item.workflow_check.check_status, 'workflow_check', 'color')
}"
class="text-24rpx"
>{{ statusFun(item.workflow_check.check_status, "workflow_check", "name") }}</view
>
</view>
</template>
<script setup>
import statusFun from "@/utils/status"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const props = defineProps({
item: Object
})
const goPath = () => {
uni.navigateTo({
url: `/pages/overtime/detail?id=${props.item.id}`
})
}
</script>

View File

@ -0,0 +1,186 @@
<template>
<view>
<CuNavbar title="加班申请">
<template #right>
<view class="text-24rpx text-white" @click="submit"></view>
</template>
</CuNavbar>
<view class="card-shadow px-base">
<uv-form labelPosition="left" :model="form" :rules="rules" ref="formRef" errorType="toast" labelWidth="250rpx">
<uv-form-item required label="日期" prop="date">
<uv-input
placeholder="请选择"
readonly
@click="openDate"
inputAlign="right"
:border="`none`"
v-model="form.date"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="加班开始时间" prop="start_at">
<uv-input
placeholder="请选择时间"
readonly
@click="openStartDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.start_at"
>
</uv-input>
</uv-form-item>
<uv-form-item required label="加班结束时间" prop="end_at">
<uv-input
placeholder="请选择时间"
readonly
@click="openEndDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.end_at"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="出差事由" prop="reason" labelPosition="top">
<uv-textarea v-model="form.reason" count placeholder="请输入" :border="`none`" :maxlength="200"></uv-textarea>
</uv-form-item>
</uv-form>
</view>
<uv-datetime-picker
placeholder="请选择日期"
v-model="dateValue"
ref="datePicker"
mode="date"
@confirm="confirmDatePicker"
>
</uv-datetime-picker>
<uv-datetime-picker
placeholder="请选择时间"
v-model="startValue"
ref="dateStartPicker"
mode="datetime"
@confirm="confirmStartDatePicker"
>
</uv-datetime-picker>
<uv-datetime-picker
v-model="endValue"
placeholder="请选择时间"
ref="dateEndPicker"
mode="datetime"
@confirm="confirmEndDatePicker"
>
</uv-datetime-picker>
<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"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const columns = ref([])
const pickerData = ref([])
const formRef = ref(null)
const dateStartPicker = ref(null)
const datePicker = ref(null)
const dateEndPicker = ref(null)
const modalRef = ref(null)
const startValue = ref(Number(new Date()))
const dateValue = ref(Number(new Date()))
const endValue = ref(Number(new Date()))
const id = ref(0)
const loading = ref(false)
const form = reactive({
start_at: "",
end_at: "",
reason: "",
date: ""
})
const openDate = () => {
datePicker.value.open()
}
const openStartDatePicker = () => {
dateStartPicker.value.open()
}
const openEndDatePicker = () => {
dateEndPicker.value.open()
}
const confirmDatePicker = e => {
form.date = timeFormat(e.value, "yyyy-mm-dd")
}
const confirmStartDatePicker = e => {
form.start_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const confirmEndDatePicker = e => {
form.end_at = timeFormat(e.value, "yyyy-mm-dd hh:MM:ss")
}
const rules = reactive({
start_at: [{ required: true, message: "请选择时间" }],
end_at: [{ required: true, message: "请选择时间" }],
reason: [{ required: true, message: "请输入加班理由" }],
date: [{ required: true, message: "请选择日期" }]
})
onLoad(options => {
id.value = options.id
if (id.value) {
http
.request({
url: `/hr/overtimes/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
startValue.value = res.start_at * 1000
endValue.value = res.end_at * 1000
dateValue.value = res.date * 1000
form.start_at = timeFormat(res.start_at, "yyyy-mm-dd hh:MM:ss")
form.end_at = timeFormat(res.end_at, "yyyy-mm-dd hh:MM:ss")
form.date = timeFormat(res.date, "yyyy-mm-dd")
form.reason = res.reason
form.address = res.address
})
}
})
const submit = () => {
formRef.value.validate().then(res => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
let url = id.value ? `/hr/overtimes/${id.value}` : "/hr/overtimes"
let method = id.value ? "PUT" : "POST"
await http.request({
url: url,
method: method,
header: {
Accept: "application/json"
},
data: {
start_at: form.start_at,
date: form.date,
reason: form.reason,
end_at: form.end_at
}
})
uni.showToast({
title: "提交成功",
icon: "none"
})
formRef.value.resetFields()
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
</script>

View File

@ -0,0 +1,119 @@
<template>
<view class="px-base" v-if="detail">
<CuNavbar title="加班审核">
<template v-if="!isEdit" #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx">
<view class="py-20rpx flex items-center justify-between">
<view>申请人</view>
<view class="text-hex-999999">{{ detail.employee.name }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">{{ detail.store.title }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">{{ detail.employee.phone }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>申请时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.created_at, "yyyy-mm-dd hh:MM") }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>加班日期</view>
<view class="text-hex-999999">{{ timeFormat(detail.date, "yyyy-mm-dd") }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>开始时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.start_at, "yyyy-mm-dd hh:MM").substring(10) }}</view>
</view>
<view class="py-20rpx flex items-center justify-between">
<view>结束时间</view>
<view class="text-hex-999999">{{ timeFormat(detail.end_at, "yyyy-mm-dd hh:MM").substring(10) }}</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>加班原因</view>
<view class="text-hex-999999 mt-20rpx">{{ detail.reason }}</view>
</view>
</view>
<view class="h-100rpx">
<view class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx">
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block> 拒绝 </uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
<uv-picker ref="pickerRef" :columns="columns" @confirm="confirmPicker"></uv-picker>
<uv-modal ref="modalRef" title="提示" content="确定删除吗?" @confirm="onSubmit" :showCancelButton="true"></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from "@/components/cu-navbar/index"
import { http } from "@/utils/request"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import { timeFormat } from "@climblee/uv-ui/libs/function/index"
const modalRef = ref(null)
const columns = [["修改", "删除"]]
const detail = ref()
const pickerRef = ref(null)
const id = ref(0)
const open = () => {
pickerRef.value.open()
}
const confirmPicker = e => {
console.log(e)
if (e.value[0] === "删除") {
modalRef.value.open()
} else {
uni.navigateTo({
url: `/pages/overtime/create?id=${id.value}`
})
}
}
const onSubmit = async () => {
try {
await http.request({
url: `/hr/overtimes/${id.value}`,
method: "DELETE",
header: {
Accept: "application/json"
}
})
uni.showToast({
title: "删除成功",
icon: "none"
})
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
onLoad(options => {
id.value = options.id
http
.request({
url: `/hr/overtimes/${options.id}`,
method: "GET",
header: {
Accept: "application/json"
}
})
.then(res => {
detail.value = res
})
})
</script>

View File

@ -0,0 +1,93 @@
<template>
<view>
<CuNavbar title="加班申请">
<template #right>
<view
@click="goPath('/pages/overtime/create')"
class="text-24rpx text-white"
>申请</view
>
</template>
</CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<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>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
:params="tabList[1].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>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import Item from './components/item.vue'
const tabList = ref([
{
name: '我的加班',
apiUrl: '/hr/overtimes',
},
{
name: '加班审核',
apiUrl: '/workflow',
params: {
subject_type: 'overtime_applies',
},
},
])
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,409 @@
<template>
<view>
<CuNavbar :isBack="isBack" title="上报"></CuNavbar>
<view
:class="[
checkPermission(['store']) && form.allow_rereport
? ''
: 'pointer-events-none',
]"
>
<uv-form
class="mt-30rpx"
labelPosition="left"
ref="formRef"
:model="form"
:rules="rules"
errorType="toast"
labelWidth="130rpx"
>
<view class="px-base space-y-20rpx">
<view class="card">
<view class="pl-20rpx">
<uv-form-item
label="日期"
prop="form.date"
@click="showDateSelect"
>
<uv-input
disabled
v-model="form.date"
disabledColor="#ffffff"
placeholder="请选择日期"
:border="`none`"
>
</uv-input>
<template v-slot:right>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-form-item>
</view>
</view>
<view class="card" v-for="(item, i) in form.items" :key="i">
<TitleComp :title="item.name"></TitleComp>
<view class="mt-20rpx">
<uv-line color="#f5f5f5"></uv-line>
</view>
<view class="pl-20rpx">
<uv-form-item label="销售" :prop="`items.${i}.sales`">
<uv-input
@input="salesChange"
v-model="item.sales"
:border="`none`"
type="digit"
:placeholder="`请输入${item.name}销售金额`"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="兑奖" :prop="`items.${i}.expenditure`">
<uv-input
@input="expenditureChange"
v-model="item.expenditure"
:border="`none`"
type="digit"
:placeholder="`请输入${item.name}兑奖金额`"
></uv-input>
</uv-form-item>
</view>
</view>
<view class="card">
<TitleComp title="汇总情况"></TitleComp>
<view class="mt-20rpx">
<uv-line color="#f5f5f5"></uv-line>
</view>
<view class="pl-20rpx">
<uv-form-item label="销售合计" prop="sales">
<uv-input
:border="`none`"
type="digit"
v-model="form.sales"
placeholder="请输入总账销售金额"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="兑奖合计" prop="expenditure">
<uv-input
type="digit"
:border="`none`"
v-model="form.expenditure"
placeholder="请输入电总账兑奖金额"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="新增客户" prop="new_customers">
<uv-input
:border="`none`"
v-model="form.new_customers"
type="number"
placeholder="请输入微信新增人数"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="交账金额" prop="handover_amount">
<uv-input
:border="`none`"
type="digit"
v-model="form.handover_amount"
placeholder="请输入交账金额"
></uv-input>
</uv-form-item>
<view class="text-primary text-24rpx py-10rpx"
>* 请确保填写的交账金额正确无误</view
>
</view>
</view>
<view class="card">
<TitleComp title="时段报表照片">
<template #right>
<view class="text-24rpx text-gray-400"
>{{ form.photos.length }}/9</view
>
</template>
</TitleComp>
<uv-form-item label="" prop="photos">
<view>
<uv-upload
width="200rpx"
height="200rpx"
:maxCount="9"
multiple
:fileList="form.photos"
@afterRead="afterRead"
@delete="deletePic"
name="photos"
></uv-upload>
</view>
</uv-form-item>
<view class="text-primary text-24rpx py-10rpx">
*竞彩时段报表照片玩法时段报表照片每日账本上传需亲笔签字销量本上传
</view>
</view>
</view>
</uv-form>
</view>
<view class="h-130rpx">
<view
class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex-center box-border px-base"
:style="style"
>
<view class="w-full">
<uv-button
:disabled="!form.allow_rereport"
type="primary"
shape="circle"
block
@click="submit"
>
上报
</uv-button>
</view>
</view>
</view>
<uv-calendars
color="#ee2c37"
confirmColor="#ee2c37"
ref="calendars"
@confirm="calendarsConfirm"
:endDate="endDate"
:date="form.date"
/>
<uv-modal
ref="modalRef"
title="提示"
content="确定提交?"
@confirm="onSubmit"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import { http } from '@/utils/request'
import CuNavbar from '@/components/cu-navbar/index'
import TitleComp from '@/components/title-comp/index'
import { addUnit, sys } from '@climblee/uv-ui/libs/function/index'
import { computed, ref, onBeforeMount, reactive, onMounted, watch } from 'vue'
import { useUserStore } from '@/store/modules/user'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
import { add } from '@/utils/index'
import checkPermission from '@/utils/permission'
import { onShow } from '@dcloudio/uni-app'
const calendars = ref(null)
const modalRef = ref(null)
const formRef = ref(null)
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo || {})
const store = computed(() => userInfo.value.store)
const endDate = timeFormat(new Date(), 'yyyy-mm-dd')
const form = reactive({
date: endDate,
items: [],
new_customers: '',
sales: '',
expenditure: '',
handover_amount: '',
photos: [],
})
const rules = reactive({
date: {
required: true,
message: '请选择日期',
trigger: ['change'],
},
handover_amount: {
required: true,
message: '请输入交账金额',
trigger: ['change'],
},
photos: {
type: 'array',
required: true,
message: '请上传时段报表照片',
},
})
const props = defineProps({
isBack: {
type: Boolean,
default: false,
},
})
const style = computed(() => {
const style = {}
style.bottom = addUnit(sys().windowBottom, 'px')
return style
})
onShow(() => {
getData(endDate)
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = async () => {
const params = {
date: form.date,
sales: form.sales,
expenditure: form.expenditure,
handover_amount: form.handover_amount,
new_customers: form.new_customers,
photos: form.photos.map((item) => item.url),
items: form.items,
}
http
.request({
url: '/ledgers',
method: 'POST',
header: {
Accept: 'application/json',
},
data: params,
})
.then((res) => {
uni.$emit('revert:submit', res)
getData()
uni.showToast({
title: '提交成功',
duration: 2000,
icon: 'none',
})
})
}
const getData = async () => {
const resData = await http.get(`/ledgers/${form.date}`)
Object.assign(form, resData, {
photos:
resData?.photos?.map((item) => {
return { url: item }
}) || [],
})
}
const getTypeList = () => {
http
.get('/keywords', {
params: {
parent_key: 'lottery_type',
},
})
.then((res) => {
form.items = res
res.forEach((item, i) => {
rules[`items.${i}.${'sales'}`] = {
required: true,
message: `请输入${item.name}销售金额`,
}
rules[`items.${i}.${'expenditure'}`] = {
required: true,
message: `请输入${item.name}兑奖金额`,
}
})
console.log(rules)
})
}
const calendarsConfirm = (e) => {
form.date = e.fulldate
getData()
}
const showDateSelect = () => {
calendars.value.open()
hideKeyboard()
}
const hideKeyboard = () => {
uni.hideKeyboard()
}
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 salesChange = () => {
const val = form?.items || []
const sales = val.reduce((a, b) => {
return add(a, b?.sales ?? 0)
}, 0)
form.sales = sales || null
}
const expenditureChange = () => {
const val = form?.items || []
const expenditure = val.reduce((a, b) => {
return add(a, b?.expenditure ?? 0)
}, 0)
form.expenditure = expenditure || null
}
watch(
() => form.items,
(val) => {
const sales = val.reduce((a, b) => {
return add(a, b?.sales ?? 0)
}, 0)
const expenditure = val.reduce((a, b) => {
return add(a, b?.expenditure ?? 0)
}, 0)
// form.sales = sales || null
// form.expenditure = expenditure || null
},
{
immediate: true,
deep: true,
}
)
</script>

View File

@ -0,0 +1,150 @@
<template>
<view class="px-base">
<CuNavbar title="举报投诉"> </CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form
labelWidth="160rpx"
:borderBottom="false"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
labelPosition="top"
>
<uv-form-item label="投诉内容" prop="content">
<uv-textarea
maxlength="200"
:customStyle="{ padding: 0 ,minHeight: '200rpx'}"
count
placeholder="可描述具体投诉内容至少20字"
:border="`none`"
v-model="form.content"
>
</uv-textarea>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="" prop="photos">
<view class="w-full">
<view class="flex justify-between text-15px">
<view>证明材料选填</view>
<view class="text-hex-999999 text-12px pr-9px">{{form.photos.length}}/9</view>
</view>
<view class="mt-10rpx">
<uv-upload
:maxCount="9"
multiple
:fileList="form.photos"
@afterRead="afterRead"
@delete="deletePic"
name="photos"
></uv-upload>
</view>
</view>
</uv-form-item>
</uv-form>
</view>
</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 Cell from '@/components/cell/index'
import { ref, reactive } from 'vue'
import { http } from '@/utils/request'
const modalRef = ref(null)
const formRef = ref(null)
const fileList = ref([])
const form = reactive({
content: '',
photos: [],
})
const rules = reactive({
content: [
{ required: true, message: '可描述具体投诉内容至少20字' },
{ min: 20, message: '至少20字' },
{ max: 200, message: '最多200字' },
],
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
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++) {
// console.log(lists[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 deletePic = (event) => {
form[event.name].splice(event.index, 1)
}
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 onSubmit = () => {
http
.post('/complaints', {
content: form.content,
photos: form.photos.map((item) => item.url),
anonymous: true
})
.then((ress) => {
uni.showToast({
title: '提交成功',
duration: 2000,
icon: 'none',
})
formRef.value.resetFields()
})
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<view class="px-base">
<CuNavbar title="设置"></CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx">
<Cell title="修改密码" :shadow="false" @onClick="goPath('/pages/setting/password')"></Cell>
<uv-line color="#f5f5f5"></uv-line>
<Cell title="举报投诉" :shadow="false" @click="goPath('/pages/setting/complain')"></Cell>
<uv-line color="#f5f5f5"></uv-line>
<Cell title="意见箱" :shadow="false" @click="goPath('/pages/setting/suggestion')"></Cell>
<uv-line color="#f5f5f5"></uv-line>
</view>
<view>
<Cell title="当前版本" :isLink="false">
<view class="flex justify-end text-hex-999999 text-24rpx px-base">
v{{ varsion }}
</view>
</Cell>
</view>
</view>
<view class="mt-100rpx">
<uv-button block type="primary">退出登录</uv-button>
</view>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import Cell from '@/components/cell/index'
import { sys } from '@climblee/uv-ui/libs/function';
import { computed } from 'vue';
const sysInfo = sys()
const varsion = computed(() => sysInfo.appVersion)
const goPath = (url) => {
uni.navigateTo({
url
})
}
</script>

View File

@ -0,0 +1,98 @@
<template>
<view class="px-base">
<CuNavbar title="修改密码">
<!-- <template #right>
<view @click="submit" class="text-white text-24rpx">保存</view>
</template> -->
</CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form
labelWidth="160rpx"
labelPosition="left"
:borderBottom="false"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
>
<uv-form-item label="新登录密码" prop="password">
<uv-input
border="none"
input-align="right"
type="password"
v-model="form.password"
placeholder="请输入新登录密码"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="新登录密码" prop="password2">
<uv-input
border="none"
v-model="form.password2"
input-align="right"
type="password"
placeholder="请输入新登录密码"
></uv-input>
</uv-form-item>
</uv-form>
</view>
</view>
<view class="mt-100rpx">
<uv-button type="primary" @click="submit"></uv-button>
</view>
<uv-modal
ref="modalRef"
title="提示"
content="确定修改密码?"
@confirm="changePassword"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import Cell from '@/components/cell/index'
import { ref, reactive } from 'vue'
import { http } from '@/utils/request'
const modalRef = ref(null)
const formRef = ref(null)
const form = reactive({
password: '',
password2: '',
})
const rules = reactive({
password: [{ required: true, message: '请输入新登录密码' }],
password2: [
{ required: true, message: '请输入新登录密码' },
{
validator: (rule, value) => {
return value === form.password
},
message: '两次密码不一致',
},
],
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
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()
})
}
</script>

View File

@ -0,0 +1,80 @@
<template>
<view class="px-base">
<CuNavbar title="意见箱"> </CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form
labelWidth="160rpx"
:borderBottom="false"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
labelPosition="top"
>
<uv-form-item label="意见内容" prop="content">
<uv-textarea
maxlength="200"
:customStyle="{ padding: 0 }"
count
placeholder="可描述具体意见内容至少20字"
border="none"
v-model="form.content"
>
</uv-textarea>
</uv-form-item>
</uv-form>
</view>
</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 Cell from '@/components/cell/index'
import { ref, reactive } from 'vue'
import { http } from '@/utils/request'
const modalRef = ref(null)
const formRef = ref(null)
const form = reactive({
content: '',
})
const rules = reactive({
content: [
{ required: true, message: '可描述具体意见内容至少20字' },
{ min: 20, message: '至少20字' },
{ max: 200, message: '最多200字' },
],
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = () => {
http
.post('/feedback', {
content: form.content,
})
.then((ress) => {
uni.showToast({
title: '提交成功',
duration: 2000,
icon: 'none',
})
formRef.value.resetFields()
})
}
</script>

View File

@ -0,0 +1,61 @@
<template>
<uv-scroll-list
:indicator="true"
indicatorColor="#fff0f0"
indicatorActiveColor="#f56c6c"
>
<view>
<template v-for="(item, i) in list" :key="i">
<view v-if="i == 0" class="flex items-center w-full text-24rpx b-solid b-b-1px">
<view class="w-140rpx text-center flex-none"> 日期 </view>
<view class="w-300rpx text-center leading-60rpx flex-none b-r-solid b-r-1px b-l-solid b-l-1px">
<view class="h-60rpx b-b-solid b-1px">总账</view>
<view class="h-60rpx grid grid-cols-3">
<view>销售</view>
<view>支出</view>
<view>新增客户</view>
</view>
</view>
<view
class="text-center leading-60rpx w-140rpx flex-none"
v-for="(ty, j) in item.lottery_types"
:key="j"
>
<view class="b-b-solid b-1px">{{ ty.name }}</view>
<view class="grid grid-cols-2">
<view>{{ ty.sales }}</view>
<view>{{ ty.expenditure }}</view>
</view>
</view>
</view>
<view
class="flex justify-between text-center text-24rpx items-center card-shadow bg-white rounded-19rpx h-80rpx"
>
<view class="flex-1 w-140rpx flex-none">{{
timeFormat(item.date, 'mm-dd')
}}</view>
<!-- 总账 -->
<view class="flex-1">{{ item.ledger.sales }}</view>
<view class="flex-1">{{ item.ledger.expenditure }}</view>
<view class="flex-1">{{ item.ledger.new_customers }}</view>
<!-- 种类 -->
<template v-for="(ty, j) in item.lottery_types" :key="j">
<view class="flex-1">{{ ty.sales }}</view>
<view class="flex-1">{{ ty.expenditure }}</view>
</template>
</view>
</template>
</view>
</uv-scroll-list>
</template>
<script setup>
import { timeFormat } from '@climblee/uv-ui/libs/function'
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
</script>

View File

@ -0,0 +1,32 @@
<template>
<view>
<view
class="flex justify-between text-center text-28rpx items-center h-80rpx"
>
<view class="flex-1">排名</view>
<view class="flex-1">门店</view>
<view class="flex-1">累计客户</view>
<view class="flex-1">销售额</view>
</view>
<view
class="flex justify-between text-center text-24rpx items-center bg-white rounded-19rpx h-80rpx"
v-for="(item, i) in list"
:key="i"
>
<view class="flex-1">{{ item.ranking }}</view>
<view class="flex-1">{{ item.store.title }}</view>
<view class="flex-1">{{ item.expenditure }}</view>
<view class="flex-1">{{ item.sales }}</view>
</view>
</view>
</template>
<script setup>
import { timeFormat } from '@climblee/uv-ui/libs/function'
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
</script>

View File

@ -0,0 +1,255 @@
<template>
<view>
<CuNavbar title="数据报表"></CuNavbar>
<view>
<uv-drop-down
ref="dropDown"
sign="dropDown_1"
text-active-color="#3c9cff"
:extra-icon="{ name: 'arrow-down-fill', color: '#666', size: '26rpx' }"
:extra-active-icon="{
name: 'arrow-up-fill',
color: '#3c9cff',
size: '26rpx',
}"
:defaultValue="['all', 'all']"
:custom-style="{ padding: '0 30rpx' }"
@click="selectMenu"
>
<uv-drop-down-item
name="area"
type="2"
:label="dropDownData.area.label"
:value="dropDownData.area.value"
>
</uv-drop-down-item>
<uv-drop-down-item
name="store"
type="2"
:label="dropDownData.store.label"
:value="dropDownData.store.value"
>
</uv-drop-down-item>
</uv-drop-down>
</view>
<view class="card">
<uv-tabs
:lineColor="'#ee2c37'"
:list="tabsList"
:scrollable="false"
:current="tabIndex"
@change="tabChange"
></uv-tabs>
<view class="text-center text-28rpx" v-if="tabIndex != 0"
>{{ currentTabs.start }} {{ currentTabs.end }}
</view>
<view class="text-center text-28rpx" v-else>{{ currentTabs.start }}</view>
<view class="flex my-20rpx items-center">
<view class="text-center flex-1">
<view>销售金额</view>
<view class="font-600">{{ ledger.sales }}</view>
</view>
<view class="h-60rpx">
<uv-line direction="col"></uv-line>
</view>
<view class="text-center flex-1">
<view>兑奖金额</view>
<view class="font-600">{{ ledger.sales }}</view>
</view>
<view class="h-60rpx">
<uv-line direction="col"></uv-line>
</view>
<view class="text-center flex-1">
<view>销售涨幅</view>
<view class="text-primary font-600"
>{{ ledger.sales_growth_rate }}%</view
>
</view>
</view>
<uv-tabs
@change="tabChange1"
:lineColor="'#ee2c37'"
:list="[{ name: '销售统计' }, { name: '门店统计' }]"
:scrollable="false"
:current="tabIndex1"
></uv-tabs>
<template v-if="tabIndex1 == 0">
<List0 :list="list"></List0>
</template>
<template v-if="tabIndex1 == 1">
<List1 :list="list"></List1>
</template>
</view>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { computed, reactive, ref } from 'vue'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
import { onShow } from '@dcloudio/uni-app'
import { http } from '@/utils/request'
import List0 from './components/list0.vue'
import List1 from './components/list1.vue'
const tabIndex1 = ref(0)
const tabIndex = ref(0)
const tabsList = ref(generateTimeArray())
const list = ref([])
const currentTabs = computed(() => tabsList.value[tabIndex.value])
const dropDownData = reactive({
area: {
label: '全部区域',
value: 'all',
activeIndex: 0,
color: '#333',
activeColor: '#2878ff',
child: [
{
label: '全部区域',
value: 'all',
},
{
label: '重庆',
value: 'cq',
},
{
label: '北京',
value: 'bj',
},
],
},
store: {
label: '全部门店',
value: 'all',
activeIndex: 0,
color: '#333',
activeColor: '#2878ff',
child: [
{
label: '全部门店',
value: 'all',
},
{
label: '门店1',
value: 'new',
},
{
label: '门店2',
value: 'money',
},
],
},
})
const result = ref([])
const ledger = ref({
expenditure: '0',
sales: '0',
sales_growth_rate: '0',
})
const activeName = ref('area')
onShow(() => {
getCount()
getList()
})
const getCount = async () => {
const resData = await http.get('/statistics/ledger', {
params: {
start_at: currentTabs.value.start,
end_at: currentTabs.value.end,
before_start_at: currentTabs.value.start,
before_end_at: currentTabs.value.end,
},
})
ledger.value = resData
}
const selectMenu = (e) => {
const { name, active, type } = e
activeName.value = name
const find = result.value.find((item) => item.name == activeName.value)
if (find) {
const findIndex = dropDownData[activeName.value].child.findIndex(
(item) => item.label == find.label && item.value == find.value
)
dropDownData[activeName.value].activeIndex = findIndex
} else {
dropDownData[activeName.value].activeIndex = 0
}
}
function generateTimeRange(name, start, end) {
start = timeFormat(start, 'yyyy-mm-dd')
end = timeFormat(end, 'yyyy-mm-dd')
return { name, start, end }
}
function getStartOfWeek(date) {
const dayOfWeek = date.getDay()
const diff = date.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1) //
return new Date(date.setDate(diff))
}
function getStartOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth(), 1)
}
function generateTimeArray() {
const today = new Date()
const yesterday = new Date(today)
yesterday.setDate(today.getDate() - 1)
const currentWeekStart = getStartOfWeek(today) //
const currentWeekEnd = new Date(today)
currentWeekEnd.setDate(currentWeekStart.getDate() + 6)
const lastWeekStart = new Date(currentWeekStart)
lastWeekStart.setDate(currentWeekStart.getDate() - 7)
const lastWeekEnd = new Date(currentWeekEnd)
lastWeekEnd.setDate(currentWeekEnd.getDate() - 7)
const currentMonthStart = getStartOfMonth(today) //
const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0)
const lastMonthStart = new Date(currentMonthStart)
lastMonthStart.setMonth(lastMonthStart.getMonth() - 1)
const lastMonthEnd = new Date(currentMonthEnd)
lastMonthEnd.setMonth(lastMonthEnd.getMonth() - 1)
return [
generateTimeRange('昨日', yesterday, yesterday),
generateTimeRange('本周', currentWeekStart, currentWeekEnd),
generateTimeRange('上周', lastWeekStart, lastWeekEnd),
generateTimeRange('本月', currentMonthStart, currentMonthEnd),
generateTimeRange('上月', lastMonthStart, lastMonthEnd),
]
}
const tabChange = (e) => {
list.value = []
tabIndex.value = e.index
getCount()
getList()
}
const tabChange1 = (e) => {
list.value = []
tabIndex1.value = e.index
getList()
}
const getList = async () => {
const url = tabIndex1.value == 0 ? '/statistics/sales' : '/statistics/stores'
const resData = await http.get(url, {
params: {
start_at: currentTabs.value.start,
end_at: currentTabs.value.end,
},
})
list.value = resData
}
</script>

View File

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

View File

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

View File

@ -0,0 +1,52 @@
<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
uni.navigateTo({
url: `/pages/task/${type}_submit?id=${props.item.id}`,
})
}
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,55 @@
<template>
<view class="px-base">
<CuNavbar title="任务详情"></CuNavbar>
<view
class="mt-30rpx card-shadow bg-white rounded-19rpx px-base text-[#333333] text-27rpx"
>
<view class="py-20rpx flex items-center justify-between">
<view>申请人</view>
<view class="text-hex-999999">测试人</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>所属门店</view>
<view class="text-hex-999999">具体门店名称</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>电话号码</view>
<view class="text-hex-999999">具体门店名称</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx flex items-center justify-between">
<view>申请时间</view>
<view class="text-hex-999999">具体门店名称</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>申请范围</view>
<view class="text-hex-999999 mt-20rpx">具体门店名称</view>
</view>
<uv-line></uv-line>
<view class="py-20rpx">
<view>清洁结果</view>
<view class="text-hex-999999 mt-20rpx">
<view class="bg-gray-50 b-solid w-130rpx h-130rpx"></view>
</view>
</view>
</view>
<view class="h-100rpx">
<view
class="fixed bottom-0 left-0 right-0 h-120rpx bg-white flex items-center px-base space-x-30rpx"
>
<view class="flex-1">
<uv-button color="#999999" shape="circle" plain block> 拒绝 </uv-button>
</view>
<view class="flex-1">
<uv-button type="primary" shape="circle" block> 通过 </uv-button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
</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

@ -0,0 +1,76 @@
<template>
<view>
<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>
<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>
</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 { 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: {
// aaa:111
},
},
])
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

View File

@ -0,0 +1,93 @@
<template>
<view class="px-base">
<CuNavbar title="任务提交"> </CuNavbar>
<view class="space-y-base mt-base">
<view class="card-shadow bg-white rounded-19rpx px-base">
<uv-form
labelWidth="160rpx"
:borderBottom="false"
:model="form"
:rules="rules"
errorType="toast"
ref="formRef"
labelPosition="top"
>
<uv-form-item label="清洁范围" required prop="content">
<uv-input
:border="`bottom`"
placeholder="请输入清洁范围"
v-model="form.content"
></uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item label="" prop="photos" required>
<view class="w-full">
<view class="flex justify-between text-15px">
<view>清洁结果</view>
<view class="text-hex-999999 text-12px pr-9px">0/9</view>
</view>
<view class="mt-10rpx">
<uv-upload></uv-upload>
</view>
</view>
</uv-form-item>
</uv-form>
</view>
</view>
<view class="mt-100rpx">
<uv-button type="primary" @click="submit"></uv-button>
</view>
<uv-modal
ref="modalRef"
title="提示"
content="确定提交投诉?"
@confirm="changePassword"
:showCancelButton="true"
></uv-modal>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import Cell from '@/components/cell/index'
import { ref, reactive } from 'vue'
import { http } from '@/utils/request'
const modalRef = ref(null)
const formRef = ref(null)
const form = reactive({
content: '',
photos: [],
})
const rules = reactive({
content: [
{ required: true, message: '请输入投诉内容' },
{ min: 20, message: '至少20字' },
{ max: 200, message: '最多200字' },
],
photos: {
type: 'array',
},
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
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()
// })
}
</script>

View File

@ -0,0 +1,163 @@
<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 {
await http.request({
url: `/tasks/${id.value}/submit`,
method: 'POST',
header: {
Accept: 'application/json',
},
data: {
task_hygiene: {
description: form.description,
photos: form.photos.map((item) => item.url),
},
},
})
uni.showToast({
title: '提交成功',
icon: 'none',
})
formRef.value.resetFields()
uni.$emit('task:submit', resData)
uni.navigateBack()
} catch (error) {
console.log(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,8 @@
<template>
<view>
<RevertPage :isBack="true"></RevertPage>
</view>
</template>
<script setup>
import RevertPage from "@/pages/revert/index";
</script>

View File

@ -0,0 +1,6 @@
<template>
<view>
<CuNavbar title="上传图片"></CuNavbar>
<uv-upload></uv-upload>
</view>
</template>

View File

@ -0,0 +1,82 @@
<template>
<view>
<CuNavbar></CuNavbar>
<!-- 文章 -->
<template v-if="info.type == 1">
<view class="h-400rpx">
<uv-image width="100%" height="100%" :src="info.cover_image" />
</view>
</template>
<!-- 视频 -->
<template v-if="info.type == 2">
<video class="w-full" :src="info.video" />
</template>
<view class="p-base space-y-10rpx">
<view class="font-500 text-30rpx">{{ info.title }}</view>
<view class="text-hex-999999">{{ info.description }}</view>
<view class="text-hex-999 text-right">{{
timeFormat(info.created_at)
}}</view>
</view>
<template v-if="info.type == 3">
<view class="p-base space-y-8rpx">
<view
v-for="item in info.files"
class="flex card-shadow bg-white rounded-19rpx p-base"
:key="item.id"
>
<view class="line-clamp-1 flex-1">
{{ item.name }}{{ item.name }}{{ item.name }}
</view>
<view @click="downloadFile(item.url)">
<uv-icon size="40rpx" name="download"></uv-icon>
</view>
</view>
</view>
</template>
<!-- 富文本 -->
<template v-if="info.content">
<uv-divider />
<view>
<uv-parse :content="info.content"></uv-parse>
</view>
</template>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { http } from '@/utils/request'
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { timeFormat } from '@climblee/uv-ui/libs/function'
const id = ref('')
const info = ref({})
onLoad((options) => {
id.value = options.id
http.get(`/train/books/${id.value}`).then((res) => {
info.value = res
})
})
const downloadFile = (url) => {
uni.downloadFile({
url: url, // URL
success: function (res) {
console.log('下载成功', res)
//
},
fail: function (err) {
console.log('下载失败', err)
//
},
})
}
</script>
<style>
page {
background-color: #fff;
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<view>
<CuNavbar title="培训课件"></CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
height="44"
:activeStyle="{ color: '#ee2c37' }"
:scrollable="true"
:current="tabIndex"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<MescrollItem
v-for="(item, key) in tabList"
:key="item.id"
:ref="`mescrollItem${item.id}`"
:top="88"
:i="key"
:index="tabIndex"
apiUrl="/train/books"
:params="{ category_id: item.id }"
>
<template v-slot="{ list }">
<view class="space-y-15rpx mt-base">
<template v-for="subItem in list" :key="subItem.id">
<view
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
@click="detail(subItem.id)"
>
<view class="flex">
<view
v-if="subItem.cover_image"
class="flex rounded-8rpx overflow-hidden"
>
<uv-image
:src="subItem.cover_image"
width="160rpx"
height="160rpx"
/>
</view>
<view
class="ml-12rpx flex-1 flex flex-col justify-between space-y-10rpx"
>
<view class="text-26rpx font-500 line-clamp-2">{{
subItem.title
}}</view>
<view class="text-24rpx text-hex-999999">{{
subItem.description
}}</view>
<view class="text-22rpx text-hex-999999 text-right">{{
timeFormat(subItem.created_at)
}}</view>
</view>
</view>
</view>
</template>
</view>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import { http } from '@/utils/request'
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import { timeFormat } from '@climblee/uv-ui/libs/function'
const mescrollItems = ref([])
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
let tabList = ref([])
http
.get('/keywords', { params: { parent_key: 'book_category' } })
.then((res) => {
tabList.value = res
})
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
const detail = (id) => {
uni.navigateTo({
url: `/pages/train-books/detail?id=${id}`,
})
}
</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

@ -0,0 +1,134 @@
<template>
<view class="p-base">
<CuNavbar title="员工详情">
<template #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<view class="mt-20rpx card-shadow px-base text-14px">
<view class="flex justify-between items-center min-h-88rpx">
<view class="text-hex-999">头像</view>
<view class="w-80rpx h-80rpx rounded-full overflow-hidden">
<image class="w-full h-full" :src="detail.avatar"></image>
</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<view class="flex justify-between items-center min-h-88rpx">
<view class="text-hex-999">姓名</view>
<view class="">{{ detail.name }}</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<view class="flex justify-between items-center min-h-88rpx">
<view class="text-hex-999">手机号</view>
<view class="">{{ detail.phone }}</view>
</view>
<uv-line color="#f5f5f5"></uv-line>
<view class="flex justify-between items-center min-h-88rpx">
<view class="text-hex-999">门店</view>
<view class="" v-if="detail.store">{{ detail.store.address }}</view>
<view class="" v-else></view>
</view>
<uv-line color="#f5f5f5"></uv-line>
</view>
<uv-action-sheet
ref="actionSheet"
:actions="actionlist"
@select="select"
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, 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({})
const actionlist = ref([
{
name: '编辑',
value: 'edit',
},
{
name: '离职',
value: 'quit',
},
{
name: '删除',
value: 'delete',
},
])
onLoad((options) => {
id.value = options.id
})
onShow(() => {
getDetail()
})
const getDetail = () => {
http.get(`/hr/employee/${id.value}`).then((res) => {
detail.value = res
})
}
const open = () => {
actionSheet.value.open()
}
const select = (e) => {
const { value } = e
if (value === 'edit') {
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

@ -0,0 +1,54 @@
<template>
<view>
<CuNavbar title="员工管理">
<template #right>
<text @click="goPage('/pages/user/update')" class="text-white"
>添加</text
>
</template>
</CuNavbar>
<StoreDropDown></StoreDropDown>
<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>
</template>
</MescrollApiOne>
</view>
</template>
<script setup>
import { ref } from 'vue'
import CuNavbar from '@/components/cu-navbar/index'
import StoreDropDown from '@/pages/home/components/store-drop-down/index.vue'
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)
onLoad(()=>{
uni.$on('user:onRefresh',onRefresh)
})
const onRefresh = () => {
// params.value = {
// city_code: 11,
// store_id: 12,
// }
mescrollItem.value.getMescroll().resetUpScroll()
}
const goPage = (url) => {
uni.navigateTo({
url,
})
}
</script>

View File

@ -0,0 +1,192 @@
<template>
<view class="p-base">
<CuNavbar :title="title">
<!-- <template #right>
<view class="text-white">保存</view>
</template> -->
</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="name">
<uv-input
placeholder="请输入姓名"
inputAlign="right"
:border="`none`"
v-model="form.name"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="手机号" prop="phone">
<uv-input
placeholder="请输入手机号"
inputAlign="right"
:border="`none`"
type="number"
maxlength="11"
v-model="form.phone"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item required label="登录用户名" prop="username">
<uv-input
placeholder="请输入登录用户名"
inputAlign="right"
v-model="form.username"
:border="`none`"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item :required="!isEdit" label="登录密码" prop="password">
<uv-input
placeholder="请输入登录密码"
inputAlign="right"
type="password"
v-model="form.password"
:border="`none`"
>
</uv-input>
</uv-form-item>
<uv-form-item :required="!isEdit" label="门店" prop="password">
</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 { mobile } from '@climblee/uv-ui/libs/function/test'
import { http } from '@/utils/request'
const formRef = ref(null)
const modalRef = ref(null)
const id = ref(0)
const loading = ref(false)
const form = reactive({
name: '',
phone: '',
username: '',
password: '',
confirm_password: '',
})
const rules = computed(() => {
return {
name: [{ required: true, message: '请输入姓名' }],
phone: [
{ required: true, message: '请输入手机号' },
{
validator: (rule, value) => {
return mobile(value)
},
message: '请输入正确的手机号',
},
],
username: [{ required: true, message: '请输入登录用户名' }],
password: [{ required: !isEdit.value, message: '请输入登录密码' }],
confirm_password: [
{ required: !isEdit.value, message: '请输入登录密码' },
{
validator: (rule, value) => {
return form.password === value
},
message: '两次密码不一致',
},
],
}
})
onLoad((options) => {
id.value = options.id
if(isEdit.value) getDetail()
})
const isEdit = computed(() => !!id.value)
const title = computed(() => (isEdit.value ? '员工修改' : '员工添加'))
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = () => {
if (isEdit.value) {
updateUser()
} else {
addUser()
}
}
const updateUser = async () => {
if (loading.value) {
return
}
loading.value = true
try {
await http.put(`/hr/employee/${id.value}`, {
...form,
})
uni.$emit('user:onRefresh')
uni.navigateBack()
formRef.value.resetFields()
uni.showToast({
title: '修改成功',
duration: 2000,
icon: 'none',
})
} catch (error) {
} finally {
loading.value = false
}
}
const addUser = async () => {
if (loading.value) {
return
}
loading.value = true
try {
await http.post('/hr/employee', {
...form,
})
formRef.value.resetFields()
uni.showToast({
title: '添加成功',
duration: 2000,
icon: 'none',
})
} catch (error) {
} finally {
loading.value = false
}
}
const getDetail = () => {
http.get(`/hr/employee/${id.value}`).then((res) => {
const info = {
name: res.name,
phone: res.phone,
username: res.username,
}
Object.assign(form, info)
})
}
</script>

View File

@ -0,0 +1,313 @@
<template>
<view>
<CuNavbar title="升职申请">
<template v-if="!isEdit" #right>
<uv-icon color="white" @click="open" name="more-dot-fill"></uv-icon>
</template>
</CuNavbar>
<uv-sticky bgColor="white">
<view class="p-base box-shadow text-28rpx">
<view class="flex justify-between px-10rpx">
<view>晋升职位</view>
<view>{{ detail?.job?.name }}</view>
</view>
<view class="flex justify-between mt-15rpx px-10rpx">
<view>申请人</view>
<view>{{ detail?.employee?.name }}</view>
</view>
<view class="flex justify-between mt-15rpx px-10rpx">
<view>推荐人</view>
<view>{{ detail?.invitor?.name }}</view>
</view>
</view>
</uv-sticky>
<view class="p-base" :class="isEdit ? '' : 'pointer-events-none'">
<view class="card-shadow px-base">
<uv-form
labelPosition="left"
:model="form"
:rules="rules"
ref="formRef"
errorType="toast"
labelWidth="250rpx"
>
<uv-form-item :required="isEdit" label="年龄" prop="age">
<uv-input
placeholder="请输入年龄"
inputAlign="right"
:border="`none`"
v-model="form.age"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item :required="isEdit" label="性别" prop="sex">
<uv-input
placeholder="请选择性别"
@click="openPicker"
readonly
inputAlign="right"
:border="`none`"
v-model="form.sex"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item :required="isEdit" label="学历" prop="education">
<uv-input
placeholder="请输入学历"
inputAlign="right"
:border="`none`"
v-model="form.education"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
:required="isEdit"
label="首次参加工作时间"
prop="first_work_time"
>
<uv-input
placeholder="请选择日期"
:required="isEdit"
@click="openDatePicker"
inputAlign="right"
:border="`none`"
v-model="form.first_work_time"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item :required="isEdit" label="工作年限" prop="work_years">
<uv-input
placeholder="请输入工作年限"
inputAlign="right"
:border="`none`"
v-model="form.work_years"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
:required="isEdit"
label="本公司工作年限"
prop="work_years_in_company"
>
<uv-input
placeholder="请输入本公司工作年限"
inputAlign="right"
:border="`none`"
v-model="form.work_years_in_company"
>
</uv-input>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
:required="isEdit"
label="员工自评"
prop="comment_self"
labelPosition="top"
>
<uv-textarea
v-model="form.comment_self"
count
placeholder="请输入员工自评"
:border="`none`"
:maxlength="200"
></uv-textarea>
</uv-form-item>
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
:required="isEdit"
label="未来计划"
prop="plans"
labelPosition="top"
>
<uv-textarea
v-model="form.plans"
count
placeholder="请输入未来计划"
:border="`none`"
:maxlength="200"
></uv-textarea>
</uv-form-item>
<template v-if="detail.promotion_status > 1">
<uv-line color="#f5f5f5"></uv-line>
<uv-form-item
label="推荐理由"
prop="reason"
:required="isEdit"
labelPosition="top"
>
<uv-textarea
v-model="form.reason"
count
placeholder="请输入推荐理由"
:border="`none`"
:maxlength="200"
></uv-textarea>
</uv-form-item>
</template>
</uv-form>
</view>
</view>
<view class="px-base" v-if="isEdit">
<view class="py-30rpx">
<uv-button type="primary" @click="submit"></uv-button>
</view>
</view>
<uv-picker
ref="pickerRef"
:columns="columns"
@confirm="confirmPicker"
></uv-picker>
<uv-datetime-picker
:minDate="50"
placeholder="请选择日期"
ref="datetimePicker"
mode="year-month"
@confirm="confirmDatePicker"
>
</uv-datetime-picker>
<uv-modal
ref="modalRef"
title="提示"
content="确定提交吗?"
@confirm="onSubmit"
:showCancelButton="true"
></uv-modal>
<uv-action-sheet
ref="actionSheet"
:actions="actionlist"
@select="select"
cancelText="取消"
>
</uv-action-sheet>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref, reactive, computed, onBeforeMount } from 'vue'
import { http } from '@/utils/request'
import { timeFormat } from '@climblee/uv-ui/libs/function/index'
const props = defineProps({
id: {
type: [Number, String],
},
isEdit: {
type: Boolean,
},
})
const actionlist = ref([
{
name: '去提交',
value: 'submit',
},
])
const actionSheet = ref(null)
const columns = [['男', '女']]
const formRef = ref(null)
const datetimePicker = ref(null)
const pickerRef = ref(null)
const modalRef = ref(null)
const loading = ref(false)
const detail = ref({})
const form = reactive({
age: '',
sex: '',
education: '',
first_work_time: '',
work_years: '',
work_years_in_company: '',
comment_self: '',
plans: '',
reason: '',
})
const openPicker = () => {
pickerRef.value.open()
}
const openDatePicker = () => {
datetimePicker.value.open()
}
const confirmDatePicker = (e) => {
form.first_work_time = timeFormat(e.value, 'yyyy-mm')
}
const confirmPicker = (e) => {
form.sex = e.value[0]
}
const rules = reactive({
age: [{ required: true, message: '请输入年龄' }],
sex: [{ required: true, message: '请选择性别' }],
education: [{ required: true, message: '请输入学历' }],
first_work_time: [{ required: true, message: '请输入首次参加工作时间' }],
work_years: [{ required: true, message: '请输入清洁范围' }],
work_years_in_company: [{ required: true, message: '请输入本公司工作年限' }],
comment_self: [{ required: true, message: '请输入员工自评' }],
plans: [{ required: true, message: '请输入未来计划' }],
reason: [{ required: true, message: '请输入推荐理由' }],
})
onBeforeMount(() => {
getDetail()
})
const submit = () => {
formRef.value.validate().then((res) => {
modalRef.value.open()
})
}
const onSubmit = async () => {
if (loading.value) return
loading.value = true
try {
const params = {
...form,
}
const resData = await http.request({
url: `/hr/promotion/${props.id}/apply`,
method: 'POST',
header: {
Accept: 'application/json',
},
data: params,
})
uni.showToast({
title: '提交成功',
icon: 'none',
})
formRef.value.resetFields()
uni.$emit('work:submit', resData)
uni.navigateBack()
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
const getDetail = async () => {
http.get(`/hr/promotion/${props.id}`).then((res) => {
detail.value = res
Object.assign(form, res?.employee_data || {})
})
}
const open = () => {
actionSheet.value.open()
}
const select = (e) => {
const { value } = e
if(value === 'submit') {
uni.navigateTo({
url: `/pages/work/create?id=${props.id}`,
})
}
}
</script>

View File

@ -0,0 +1,66 @@
<template>
<view
@click="onClick"
class="card-shadow bg-white rounded-19rpx p-base space-y-10rpx"
>
<view class="flex items-center justify-between">
<view class="text-30rpx"> {{ item.job.name }}</view>
<view
class="text-24rpx"
:style="{
color: statusFun(item.promotion_status, 'promotion_status', 'color'),
}"
>{{ item.promotion_status_text }}</view
>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">推荐人</view>
<view class="">{{ item?.invitor?.name }}</view>
</view>
<view class="text-24rpx text-hex-999999 flex">
<view class="w-140rpx">晋升职位</view>
<view class="">{{ item.job.name }}</view>
</view>
<!-- <template v-if="options?.includes(item.promotion_status)">
<view class="py-10rpx">
<uv-line color="#f5f5f5"></uv-line>
</view>
<view class="flex justify-end items-center">
<uv-button type="primary" size="mini">去提交</uv-button>
</view>
</template> -->
</view>
</template>
<script setup>
import statusFun from '@/utils/status'
const props = defineProps({
item: Object,
options: {
type: Array,
default: () => [],
},
type: {
type: Number,
default: 0,
},
subject_type: String,
})
const onOption = (e) => {}
const onClick = () => {
let url
if (props.type == 3) {
} else {
url = `/pages/work/detail?id=${props.item.id}`
}
goPath(url)
}
const goPath = (url) => {
uni.navigateTo({
url,
})
}
</script>

View File

@ -0,0 +1,15 @@
<template>
<view>
<commone :id="id" isEdit></commone>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import commone from './commone.vue'
import { ref } from 'vue'
const id = ref(0)
onLoad((options) => {
id.value = options.id
})
</script>

View File

@ -0,0 +1,16 @@
<template>
<view>
<commone :id="id"></commone>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import commone from './commone.vue'
import { ref } from 'vue'
const id = ref(0)
onLoad((options) => {
id.value = options.id
})
</script>

View File

@ -0,0 +1,114 @@
<template>
<view>
<CuNavbar title="升职申请"> </CuNavbar>
<uv-sticky bgColor="#fff">
<uv-tabs
:activeStyle="{ color: '#ee2c37' }"
:scrollable="false"
lineColor="#ee2c37"
:list="tabList"
@change="tabChange"
></uv-tabs>
</uv-sticky>
<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 :type="0" :item="item" :options="[1]"> </Item>
</view>
</view>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem1"
:top="88"
:i="1"
:index="tabIndex"
:apiUrl="tabList[1].apiUrl"
>
<template v-slot="{ list }">
<view class="space-y-15rpx p-base">
<view v-for="(item, i) in list" :key="i">
<Item :type="1" :item="item" :options="[2]"> </Item>
</view>
</view>
</template>
</MescrollItem>
<MescrollItem
ref="mescrollItem2"
:top="88"
:i="2"
:index="tabIndex"
:apiUrl="tabList[2].apiUrl"
:params="tabList[2].params"
>
<template v-slot="{ list }">
<view class="space-y-15rpx p-base">
<view v-for="(item, i) in list" :key="i">
<Item
:type="2"
:subject_type="tabList[2].params.subject_type"
:item="item"
></Item>
</view>
</view>
</template>
</MescrollItem>
</view>
</template>
<script setup>
import CuNavbar from '@/components/cu-navbar/index'
import { ref } from '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'
import Item from './components/item.vue'
import { onLoad } from '@dcloudio/uni-app'
const tabList = ref([
{
name: '申请列表',
apiUrl: '/hr/promotion/apply',
},
{
name: '推荐列表',
apiUrl: '/hr/promotion/apply',
},
{
name: '审核列表',
apiUrl: '/workflow',
params: {
subject_type: 'employee_promotions',
},
},
])
const mescrollItem0 = ref(null)
const mescrollItem1 = ref(null)
const mescrollItem2 = ref(null)
const mescrollItems = [mescrollItem0, mescrollItem1, mescrollItem2]
const { tabIndex, getMescroll, scrollToLastY } = useMescrollMore(
mescrollItems,
onPageScroll,
onReachBottom
)
onLoad(() => {
uni.$on('work:submit', getMescroll(tabIndex.value))
})
const goPath = (url) => {
uni.navigateTo({
url,
})
}
const tabChange = ({ index }) => {
tabIndex.value = index
scrollToLastY()
}
</script>

6
src/shime-uni.d.ts vendored 100644
View File

@ -0,0 +1,6 @@
export {};
declare module "vue" {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {}
}

View File

@ -0,0 +1,21 @@
page {
background-color: #f8f8f8;
}
.card {
background: #ffffff;
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.04);
border-radius: 10px;
padding: 20rpx;
}
.card-shadow {
background: #ffffff;
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.04);
border-radius: 20rpx;
}
.box-shadow {
background: #ffffff;
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.04);
}

View File

@ -0,0 +1,47 @@
// uvUIuni.scss
// uni.scss
// uni.scssscssmain.jsApp.vue
$uv-main-color: #303133;
$uv-content-color: #606266;
$uv-tips-color: #909193;
$uv-light-color: #c0c4cc;
$uv-border-color: #dadbde;
$uv-bg-color: #f3f4f6;
$uv-disabled-color: #c8c9cc;
$uv-primary: #ee2c37;
$uv-primary-dark: #398ade;
$uv-primary-disabled: #9acafc;
$uv-primary-light: #ecf5ff;
$uv-warning: #f9ae3d;
$uv-warning-dark: #f1a532;
$uv-warning-disabled: #f9d39b;
$uv-warning-light: #fdf6ec;
$uv-success: #5ac725;
$uv-success-dark: #53c21d;
$uv-success-disabled: #a9e08f;
$uv-success-light: #f5fff0;
$uv-error: #f56c6c;
$uv-error-dark: #e45656;
$uv-error-disabled: #f7b2b2;
$uv-error-light: #fef0f0;
$uv-info: #909399;
$uv-info-dark: #767a82;
$uv-info-disabled: #c4c6c9;
$uv-info-light: #f4f4f5;
@mixin flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: $direction;
}
.uv-navbar--fixed{
z-index: 1000 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>home3_icon_back_def</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon" transform="translate(-48.000000, -215.000000)">
<g id="home3_icon_back_def" transform="translate(48.000000, 215.000000)">
<rect id="矩形" fill="#000000" fill-rule="nonzero" opacity="0" x="0" y="0" width="28" height="28"></rect>
<g id="back" transform="translate(13.500000, 14.500000) scale(-1, 1) translate(-13.500000, -14.500000) translate(6.000000, 8.000000)" stroke="#333333" stroke-linecap="round" stroke-width="2">
<path d="M5,10 L8.59692142,5.91374903 C9.91060843,4.42134644 12.1853935,4.27646884 13.6777961,5.59015586 C13.7456217,5.64985929 13.8111753,5.71209516 13.8743179,5.77673125 L18,10 L18,10" id="路径-5" stroke-linejoin="round" transform="translate(11.500000, 6.500000) rotate(-270.000000) translate(-11.500000, -6.500000) "></path>
<line x1="0" y1="6.5" x2="8" y2="6.5" id="直线-3"></line>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>home3_icon_back_def</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon" transform="translate(-48.000000, -215.000000)">
<g id="home3_icon_back_def" transform="translate(48.000000, 215.000000)">
<rect id="矩形" fill="#fff" fill-rule="nonzero" opacity="0" x="0" y="0" width="28" height="28"></rect>
<g id="back" transform="translate(13.500000, 14.500000) scale(-1, 1) translate(-13.500000, -14.500000) translate(6.000000, 8.000000)" stroke="#fff" stroke-linecap="round" stroke-width="2">
<path d="M5,10 L8.59692142,5.91374903 C9.91060843,4.42134644 12.1853935,4.27646884 13.6777961,5.59015586 C13.7456217,5.64985929 13.8111753,5.71209516 13.8743179,5.77673125 L18,10 L18,10" id="路径-5" stroke-linejoin="round" transform="translate(11.500000, 6.500000) rotate(-270.000000) translate(-11.500000, -6.500000) "></path>
<line x1="0" y1="6.5" x2="8" y2="6.5" id="直线-3"></line>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>me_icon_more_def</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon" transform="translate(-238.000000, -264.000000)">
<g id="me_icon_more_def" transform="translate(238.000000, 264.000000)">
<rect id="矩形" x="0" y="0" width="14" height="14"></rect>
<g id="编组-9" transform="translate(0.000000, 1.000000)" stroke="#333333" stroke-linecap="round">
<path d="M4.5,9.5 L7.97809825,5.21946514 C9.02292793,3.93358143 10.9123445,3.73816853 12.1982282,4.78299821 C12.3183328,4.88058781 12.4307062,4.98732445 12.5343422,5.10225239 L16.5,9.5 L16.5,9.5" id="路径-5" stroke-linejoin="round" transform="translate(10.500000, 6.000000) rotate(-270.000000) translate(-10.500000, -6.000000) "></path>
<line x1="0.5" y1="6" x2="7.5" y2="6" id="直线"></line>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More