xiongzhu 2 роки тому
батько
коміт
1ae469fd92

+ 93 - 0
src/components/EditDialog.vue

@@ -0,0 +1,93 @@
+<template>
+    <ElDialog
+        v-model="show"
+        :title="title"
+        class="!w-10/12 max-w-3xl"
+        :close-on-click-modal="!saving"
+        :close-on-press-escape="!saving"
+    >
+        <ElForm :model="model" :rules="rules" ref="formEl" label-position="right" :label-width="labelWidth">
+            <slot></slot>
+        </ElForm>
+        <div class="text-right">
+            <ElButton @click="show = false" :disabled="saving">取消</ElButton>
+            <ElButton type="primary" @click="onSave" :loading="saving">保存</ElButton>
+        </div>
+    </ElDialog>
+</template>
+<script setup>
+import { computed, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+const props = defineProps({
+    modelValue: {
+        type: Boolean,
+        default: false
+    },
+    title: {
+        type: String,
+        default: '编辑'
+    },
+    model: {
+        type: Object,
+        default: () => ({})
+    },
+    rules: {
+        type: Object,
+        default: () => ({})
+    },
+    onSubmit: {
+        type: Function,
+        default: () => {}
+    },
+    labelWidth: {
+        type: String,
+        default: '80px'
+    }
+})
+const emit = defineEmits(['update:modelValue', 'success', 'error'])
+const show = computed({
+    get() {
+        return props.modelValue
+    },
+    set(modelValue) {
+        emit('update:modelValue', modelValue)
+    }
+})
+
+const formEl = ref(null)
+const saving = ref(false)
+async function onSave() {
+    try {
+        try {
+            await formEl.value.validate()
+        } catch (e) {
+            return
+        }
+        saving.value = true
+        await props.onSubmit()
+        saving.value = false
+        show.value = false
+        emit('success')
+    } catch (e) {
+        saving.value = false
+        if (e.message) {
+            if (typeof e.message === 'string') {
+                ElMessage.error(e.message)
+            } else {
+                ElMessage.error(JSON.stringify(e.message))
+            }
+        } else {
+            ElMessage.error('保存失败')
+        }
+        emit('error', e)
+    }
+}
+watch(
+    () => props.modelValue,
+    (modelValue) => {
+        if (modelValue && formEl.value) {
+            formEl.value.clearValidate()
+        }
+    }
+)
+</script>

+ 12 - 1
src/components/EnumSelect.vue

@@ -1,5 +1,12 @@
 <template>
-    <ElSelect :modelValue="modelValue" @change="change" :placeholder="placeholder" :clearable="clearable" @clear="clear">
+    <ElSelect
+        :modelValue="modelValue"
+        @change="change"
+        :placeholder="placeholder"
+        :clearable="clearable"
+        @clear="clear"
+        :multiple="multiple"
+    >
         <ElOption v-for="item in options" :key="item.value" :value="item.value" :label="item.label" />
     </ElSelect>
 </template>
@@ -19,6 +26,10 @@ const props = defineProps({
     clearable: {
         type: Boolean,
         default: true
+    },
+    multiple: {
+        type: Boolean,
+        default: false
     }
 })
 const emit = defineEmits(['update:modelValue'])

+ 2 - 1
src/components/PagingTable.vue

@@ -109,7 +109,8 @@ defineExpose({
 <style lang="less" scoped>
 .filter {
     :deep(> *) {
-        margin-bottom: 10px;
+        margin-bottom: 15px;
+        margin-right: 15px;
     }
 }
 </style>

+ 6 - 0
src/enums/index.js

@@ -19,3 +19,9 @@ export const MemberType = {
     TRIAL: '试用会员',
     PAID: '付费会员'
 }
+
+export const UserRole = {
+    user: '普通用户',
+    admin: '管理员',
+    api: 'API用户'
+}

+ 9 - 0
src/utils/editDialog.js

@@ -0,0 +1,9 @@
+import { defineProps, ref } from 'vue'
+export function setupEditDialog(model) {
+    const showEditDialog = ref(false)
+    function onEdit(row) {
+        model.value = row ? { ...row } : {}
+        showEditDialog.value = true
+    }
+    return { showEditDialog, onEdit }
+}

+ 60 - 1
src/views/UserView.vue

@@ -1,14 +1,73 @@
 <template>
-    <PagingTable url="/admin/users" :where="{ roles: 'user' }">
+    <PagingTable url="/admin/users" :where="where" ref="table">
+        <template #filter>
+            <EnumSelect :enum="UserRole" v-model="where.roles"></EnumSelect>
+            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+        </template>
         <ElTableColumn prop="id" label="#" width="80" />
         <ElTableColumn prop="username" label="用户名" min-width="120" />
         <ElTableColumn prop="name" label="昵称" min-width="120" />
         <ElTableColumn prop="phone" label="手机" min-width="120" />
         <ElTableColumn prop="createdAt" label="注册时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn prop="invitor" label="上级" />
+        <ElTableColumn label="操作" align="center" width="120">
+            <template #default="{ row }">
+                <ElButton @click="getToken(row)">Token</ElButton>
+            </template>
+        </ElTableColumn>
     </PagingTable>
+    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="table.refresh()">
+        <ElFormItem prop="username" label="用户名">
+            <ElInput v-model="model.username" placeholder="请输入用户名" />
+        </ElFormItem>
+        <ElFormItem prop="name" label="昵称">
+            <ElInput v-model="model.name" placeholder="请输入昵称" />
+        </ElFormItem>
+        <ElFormItem prop="phone" label="手机">
+            <ElInput v-model="model.phone" placeholder="请输入手机" />
+        </ElFormItem>
+        <ElFormItem prop="password" label="密码">
+            <ElInput v-model="model.password" placeholder="请输入密码" />
+        </ElFormItem>
+        <ElFormItem prop="roles" label="角色">
+            <EnumSelect v-model="model.roles" :enum="UserRole" />
+        </ElFormItem>
+    </EditDialog>
 </template>
 <script setup>
+import { ref } from 'vue'
 import PagingTable from '@/components/PagingTable.vue'
 import { useTimeFormatter } from '@/utils/formatter'
+import { Plus } from '@vicons/tabler'
+import EditDialog from '@/components/EditDialog.vue'
+import { setupEditDialog } from '@/utils/editDialog'
+import EnumSelect from '@/components/EnumSelect.vue'
+import { UserRole } from '@/enums'
+import { http } from '@/plugins/http'
+import { ElMessage } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+
+const where = ref({ roles: 'user' })
 const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
+    phone: [{ required: true, message: '请输入手机', trigger: 'blur' }],
+    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+    roles: [{ required: true, message: '请选择角色', trigger: 'blur' }]
+}
+const { showEditDialog, onEdit } = setupEditDialog(model)
+async function submit() {
+    await http.put('/admin/users', model.value)
+    ElMessage.success('保存成功')
+}
+function getToken(row) {
+    http.get(`/auth/admin/user/${row.id}/token`).then((res) => {
+        const { copy } = useClipboard({ legacy: true })
+        copy(res.access_token)
+        ElMessage.success('复制成功')
+    })
+}
 </script>