248 lines
7.8 KiB
Vue
248 lines
7.8 KiB
Vue
<template>
|
||
<view class="staff-page">
|
||
<!-- 导航栏 -->
|
||
<view class="nav-bar">
|
||
<text class="nav-back" @click="goBack">‹</text>
|
||
<text class="nav-title">员工管理</text>
|
||
<view style="width:40px;"></view>
|
||
</view>
|
||
|
||
<view class="invite-code-row">
|
||
<text class="invite-label">员工邀请码</text>
|
||
<text class="invite-code">{{ storeInfo.inviteCode }}</text>
|
||
<view class="copy-btn" @click="copyCode">复制</view>
|
||
</view>
|
||
|
||
<view class="add-btn-wrap">
|
||
<view class="btn-add" @click="showAddStaff = true">+ 新增员工</view>
|
||
</view>
|
||
|
||
<!-- 员工列表 -->
|
||
<view class="staff-list">
|
||
<view v-for="s in staffList" :key="s.id" class="staff-item">
|
||
<view class="staff-avatar" :style="avatarStyle(s)">
|
||
<image v-if="s.avatar" :src="s.avatar" class="avatar-img" />
|
||
<text v-else class="avatar-initials">{{ s.name ? s.name[0] : '?' }}</text>
|
||
</view>
|
||
<view class="staff-info">
|
||
<text class="staff-name">{{ s.name }}</text>
|
||
<text class="staff-phone">{{ s.phone }}</text>
|
||
<text class="staff-role">{{ s.role === 'boss' ? '🏠 店长' : '👤 员工' }}</text>
|
||
</view>
|
||
<text v-if="s.role !== 'boss'" class="delete-btn" @click="deleteStaff(s.id)">删除</text>
|
||
</view>
|
||
<view v-if="staffList.length === 0" class="empty-wrap">
|
||
<text class="empty-icon">👥</text>
|
||
<text class="empty-text">暂无员工</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 新增员工弹窗 -->
|
||
<view v-if="showAddStaff" class="dialog-mask" @click="showAddStaff = false">
|
||
<view class="dialog-content" @click.stop>
|
||
<view class="dialog-header">
|
||
<text class="dialog-title">新增员工</text>
|
||
<text class="dialog-close" @click="showAddStaff = false">✕</text>
|
||
</view>
|
||
<view class="dialog-body">
|
||
<view class="form-item">
|
||
<text class="form-label">员工姓名</text>
|
||
<input class="form-input" v-model="newStaff.name" placeholder="请输入" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">手机号</text>
|
||
<input class="form-input" v-model="newStaff.phone" type="tel" placeholder="请输入" maxlength="11" />
|
||
</view>
|
||
</view>
|
||
<view class="dialog-footer">
|
||
<view class="btn-cancel" @click="showAddStaff = false">取消</view>
|
||
<view class="btn-confirm" @click="onAddStaffConfirm">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { getStaffList, createStaff, deleteStaff as delStaff } from '../../../utils/api.js'
|
||
|
||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||
|
||
const staffList = ref([])
|
||
const showAddStaff = ref(false)
|
||
const newStaff = ref({ name: '', phone: '' })
|
||
|
||
const COLORS = ['#ff7c43', '#07c160', '#8b6914', '#e06040', '#5090d0', '#9b59b6']
|
||
const avatarStyle = (s) => {
|
||
if (s.avatar) return {}
|
||
const idx = (s.name?.charCodeAt(0) || 0) % COLORS.length
|
||
return { background: COLORS[idx] }
|
||
}
|
||
|
||
const goBack = () => uni.navigateBack()
|
||
|
||
const loadStaff = async () => {
|
||
const res = await getStaffList(storeInfo.id)
|
||
if (res.code === 200) staffList.value = res.data
|
||
}
|
||
|
||
const copyCode = () => {
|
||
uni.setClipboardData({ data: storeInfo.inviteCode, success: () => uni.showToast({ title: '邀请码已复制', icon: 'success' }) })
|
||
}
|
||
|
||
const onAddStaffConfirm = async () => {
|
||
if (!newStaff.value.name) { uni.showToast({ title: '请输入员工姓名', icon: 'none' }); return }
|
||
if (!newStaff.value.phone || newStaff.value.phone.length !== 11) { uni.showToast({ title: '请输入正确的手机号', icon: 'none' }); return }
|
||
const res = await createStaff({ storeId: storeInfo.id, name: newStaff.value.name, phone: newStaff.value.phone })
|
||
if (res.code === 200) {
|
||
uni.showToast({ title: `添加成功,密码:${res.data.password}`, icon: 'none', duration: 3000 })
|
||
showAddStaff.value = false
|
||
newStaff.value = { name: '', phone: '' }
|
||
loadStaff()
|
||
} else {
|
||
uni.showToast({ title: res.message || '添加失败', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
const deleteStaff = async (staffId) => {
|
||
uni.showModal({
|
||
title: '确认删除',
|
||
content: '确定删除该员工?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
const r = await delStaff(staffId)
|
||
if (r.code === 200) {
|
||
uni.showToast({ title: '已删除', icon: 'success' })
|
||
loadStaff()
|
||
} else {
|
||
uni.showToast({ title: r.message || '删除失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
onMounted(() => loadStaff())
|
||
</script>
|
||
|
||
<style scoped>
|
||
.staff-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
|
||
|
||
.nav-bar {
|
||
background: #fff;
|
||
padding: 40px 16px 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||
|
||
.invite-code-row {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #fff;
|
||
margin: 12px 16px;
|
||
padding: 14px 16px;
|
||
border-radius: 12px;
|
||
gap: 8px;
|
||
}
|
||
.invite-label { font-size: 14px; color: #666; }
|
||
.invite-code { flex: 1; font-size: 15px; color: #07c160; font-weight: 600; font-family: monospace; }
|
||
.copy-btn { font-size: 13px; color: #07c160; padding: 4px 12px; border: 1px solid #07c160; border-radius: 4px; }
|
||
|
||
.add-btn-wrap { padding: 0 16px 12px; }
|
||
.btn-add {
|
||
width: 100%;
|
||
height: 44px;
|
||
background: #07c160;
|
||
color: #fff;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 15px;
|
||
}
|
||
|
||
.staff-list { padding: 0 16px; }
|
||
.staff-item {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 10px;
|
||
gap: 12px;
|
||
}
|
||
.staff-avatar {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
color: #fff;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
.avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
||
.avatar-initials { font-weight: 600; }
|
||
.staff-info { flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
||
.staff-name { font-size: 15px; font-weight: 500; color: #333; }
|
||
.staff-phone { font-size: 12px; color: #999; }
|
||
.staff-role { font-size: 11px; color: #999; }
|
||
.delete-btn { font-size: 13px; color: #ff4d4f; }
|
||
|
||
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
|
||
.empty-icon { font-size: 48px; }
|
||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||
|
||
/* 弹窗 */
|
||
.dialog-mask {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.dialog-content {
|
||
width: 80%;
|
||
max-width: 340px;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
.dialog-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.dialog-title { font-size: 16px; font-weight: 600; color: #333; }
|
||
.dialog-close { font-size: 18px; color: #999; }
|
||
.dialog-body { padding: 16px 20px; }
|
||
.dialog-footer { display: flex; border-top: 1px solid #eee; }
|
||
.btn-cancel, .btn-confirm { flex: 1; text-align: center; padding: 14px 0; font-size: 15px; }
|
||
.btn-cancel { color: #666; border-right: 1px solid #eee; }
|
||
.btn-confirm { color: #07c160; font-weight: 600; }
|
||
|
||
.form-item { margin-bottom: 16px; }
|
||
.form-item:last-child { margin-bottom: 0; }
|
||
.form-label { font-size: 13px; color: #999; display: block; margin-bottom: 6px; }
|
||
.form-input {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: 1px solid #eee;
|
||
border-radius: 8px;
|
||
padding: 0 12px;
|
||
font-size: 14px;
|
||
box-sizing: border-box;
|
||
}
|
||
</style>
|