Răsfoiți Sursa

首页内容修改

panhui 2 ani în urmă
părinte
comite
43cb158c0f

+ 48 - 0
src/router/index.js

@@ -181,6 +181,54 @@ const router = createRouter({
                     meta: {
                         title: '席位管理'
                     }
+                },
+                {
+                    path: 'homeWeb',
+                    name: 'homeWeb',
+                    component: () => import('../views/web/HomeWebView.vue'),
+                    meta: {
+                        title: '首页排版'
+                    }
+                },
+                {
+                    path: 'connect',
+                    name: 'connect',
+                    component: () => import('../views/web/ConnectView.vue'),
+                    meta: {
+                        title: '联系客户'
+                    }
+                },
+                {
+                    path: 'case',
+                    name: 'case',
+                    component: () => import('../views/web/CaseView.vue'),
+                    meta: {
+                        title: '案例'
+                    }
+                },
+                {
+                    path: 'homeCase',
+                    name: 'homeCase',
+                    component: () => import('../views/web/HomeCaseView.vue'),
+                    meta: {
+                        title: '首页案例'
+                    }
+                },
+                {
+                    path: 'question',
+                    name: 'question',
+                    component: () => import('../views/web/QuestionView.vue'),
+                    meta: {
+                        title: '常见问题'
+                    }
+                },
+                {
+                    path: 'ability',
+                    name: 'ability',
+                    component: () => import('../views/web/AbilityView.vue'),
+                    meta: {
+                        title: '首页内容定制'
+                    }
                 }
             ]
         }

+ 33 - 1
src/views/MainView.vue

@@ -66,7 +66,8 @@ import {
     Users,
     License,
     Database,
-    Building
+    Building,
+    Ad2
 } from '@vicons/tabler'
 import UserAvatar from '@/components/UserAvatar.vue'
 import ChangePwd from '@/components/ChangePwd.vue'
@@ -103,6 +104,37 @@ const menus = computed(() => {
                     }
                 ]
             },
+            {
+                name: 'web',
+                title: '官网管理',
+                icon: Ad2,
+                children: [
+                    {
+                        name: 'homeWeb',
+                        title: '首页模块'
+                    },
+                    {
+                        name: 'ability',
+                        title: '首页内容定制'
+                    },
+                    {
+                        name: 'homeCase',
+                        title: '首页案例'
+                    },
+                    {
+                        name: 'question',
+                        title: '常见问题'
+                    },
+                    {
+                        name: 'case',
+                        title: '案例'
+                    },
+                    {
+                        name: 'connect',
+                        title: '联系客户'
+                    }
+                ]
+            },
             {
                 name: 'org-parent',
                 title: '企业管理',

+ 161 - 0
src/views/web/AbilityView.vue

@@ -0,0 +1,161 @@
+<template>
+    <PagingTable url="/web/ability" :where="where" ref="table">
+        <template #filter>
+            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" /><ElTableColumn
+            prop="logo"
+            label="LOGO"
+            min-width="80"
+            align="center"
+        >
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.logo" v-if="row.logo" style="width: 50px; height: 50px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="name" label="标题" min-width="120" />
+        <ElTableColumn prop="desc" label="内容" min-width="120" />
+        <ElTableColumn prop="webId" label="首页id" align="center" min-width="50" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" width="150" fixed="right">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+                <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="logo" label="LOGO">
+            <SingleUpload v-model="model.logo" />
+        </ElFormItem>
+        <ElFormItem prop="name" label="标题">
+            <ElInput v-model="model.name" placeholder="请输入标题" />
+        </ElFormItem>
+        <ElFormItem prop="desc" label="内容">
+            <ElInput type="textarea" v-model="model.desc" placeholder="请输入内容" autosize />
+        </ElFormItem>
+
+        <ElFormItem prop="webId" label="首页Id">
+            <ElInput v-model="model.webId" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin')
+        return {
+            del: 0
+        }
+    return {
+        del: 0,
+        orgId: user.value.orgId
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }]
+}
+const { showEditDialog } = setupEditDialog(model)
+
+function onEdit(row) {
+    model.value = {
+        ...row
+    }
+    showEditDialog.value = true
+}
+
+async function delVal(index) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        model.value.values.splice(index, 1)
+        ElMessage.success('删除成功')
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+function addVal() {
+    model.value.values.push('')
+}
+
+async function submit() {
+    let data = { ...model.value }
+    if (model.value.sub) {
+        data.name = model.value.name + '_;' + model.value.sub
+    }
+    data.val = model.value.values
+        .filter((item) => {
+            return !!item && item !== ' '
+        })
+        .join('_;')
+    delete data.sub
+    delete data.values
+    console.log(data)
+
+    await http.put(data.id ? `/web/ability/save/${data.id}` : '/web/ability/save', data)
+    ElMessage.success('保存成功')
+    table.value.refresh()
+}
+function getToken(row) {
+    http.get(`/auth/admin/user/${row.id}/token`).then((res) => {
+        const { copy } = useClipboard({ legacy: true })
+        copy(res.access_token)
+        ElMessage.success('复制成功')
+    })
+}
+
+async function deleteRow(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/web/ability/del/${row.id}`)
+        ElMessage.success('删除成功')
+        table.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.form-input {
+    width: 100%;
+    display: flex;
+
+    .el-input + .el-button {
+        margin-left: 20px;
+    }
+}
+.form-input + .form-input {
+    margin-top: 10px;
+}
+</style>

+ 227 - 0
src/views/web/CaseView.vue

@@ -0,0 +1,227 @@
+<template>
+    <PagingTable url="/web/cases/list" :where="where" ref="table">
+        <template #filter>
+            <!-- <ElButton :icon="Plus" @click="onEdit()">添加</ElButton> -->
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="name" label="案例名称" min-width="120" />
+        <ElTableColumn prop="logo" label="LOGO" min-width="80" align="center">
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.logo" v-if="row.logo" style="width: 30px; height: 30px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="type" label="行业" min-width="80" />
+        <ElTableColumn prop="tags" label="标签" show-overflow-tooltip min-width="120" />
+        <ElTableColumn prop="desc" label="描述" show-overflow-tooltip min-width="120" />
+        <ElTableColumn prop="info" label="客户概况" show-overflow-tooltip min-width="120" />
+        <ElTableColumn prop="needs" label="客户需求" show-overflow-tooltip min-width="120" />
+        <ElTableColumn prop="solution" label="解决方案" show-overflow-tooltip min-width="120" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" fixed="right" width="150">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+                <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="name" label="标题">
+            <ElInput v-model="model.name" placeholder="请输入标题" />
+        </ElFormItem>
+        <ElFormItem prop="logo" label="LOGO">
+            <SingleUpload v-model="model.logo" />
+        </ElFormItem>
+        <ElFormItem prop="type" label="行业">
+            <ElInput v-model="model.type" placeholder="请输入行业" />
+        </ElFormItem>
+        <ElFormItem prop="tags" label="标签">
+            <div class="form-input" v-for="(item, index) in model.tags" :key="index">
+                <ElInput type="text" v-model="model.tags[index]" placeholder="请输入内容" />
+                <el-button type="danger" size="mini" plain @click="delVal(index, 'tags')">删除</el-button>
+            </div>
+            <div class="form-input">
+                <el-button type="primary" size="mini" plain @click="addVal('tags')">新增</el-button>
+            </div>
+        </ElFormItem>
+        <ElFormItem prop="desc" label="描述">
+            <ElInput type="textarea" v-model="model.desc" placeholder="请输入描述" autosize />
+        </ElFormItem>
+        <ElFormItem prop="info" label="客户概况">
+            <div class="form-input" v-for="(item, index) in model.info" :key="index">
+                <ElInput type="text" v-model="model.info[index].name" placeholder="请输入内容" />
+                <ElInput type="text" v-model="model.info[index].val" placeholder="请输入内容" />
+                <el-button type="danger" size="mini" plain @click="delVal(index, 'info')">删除</el-button>
+            </div>
+            <div class="form-input">
+                <el-button type="primary" size="mini" plain @click="addVal('info')">新增</el-button>
+            </div>
+        </ElFormItem>
+        <ElFormItem prop="needs" label="客户需求">
+            <div class="form-input" v-for="(item, index) in model.needs" :key="index">
+                <ElInput type="text" v-model="model.needs[index]" placeholder="请输入内容" />
+                <el-button type="danger" size="mini" plain @click="delVal(index, 'needs')">删除</el-button>
+            </div>
+            <div class="form-input">
+                <el-button type="primary" size="mini" plain @click="addVal('needs')">新增</el-button>
+            </div>
+        </ElFormItem>
+        <ElFormItem prop="solution" label="解决方案">
+            <div class="form-input" v-for="(item, index) in model.solution" :key="index">
+                <ElInput type="text" v-model="model.solution[index]" placeholder="请输入内容" />
+                <el-button type="danger" size="mini" plain @click="delVal(index, 'solution')">删除</el-button>
+            </div>
+            <div class="form-input">
+                <el-button type="primary" size="mini" plain @click="addVal('solution')">新增</el-button>
+            </div>
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin')
+        return {
+            del: 0
+        }
+    return {
+        orgId: user.value.orgId,
+        del: 0
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }]
+}
+const { showEditDialog } = setupEditDialog(model)
+
+function onEdit(row) {
+    model.value = {
+        ...row,
+        tags: row.tags.split(','),
+        info: row.info.split('_;').map((_info) => {
+            let vals = _info.split('_,')
+            return {
+                name: vals.length > 0 ? vals[0] : '',
+                val: vals.length > 1 ? vals[1] : ''
+            }
+        }),
+        needs: row.needs.split('_;'),
+        solution: row.solution.split('_;')
+    }
+    showEditDialog.value = true
+}
+
+async function delVal(index, key) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        model.value[key].splice(index, 1)
+        ElMessage.success('删除成功')
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+function addVal(key) {
+    if (key === 'info') {
+        model.value[key].push({ name: '', val: '' })
+    } else {
+        model.value[key].push('')
+    }
+}
+
+async function submit() {
+    let data = { ...model.value }
+    data.tags = model.value.tags
+        .filter((item) => {
+            return !!item && item !== ' '
+        })
+        .join(',')
+    data.needs = model.value.needs
+        .filter((item) => {
+            return !!item && item !== ' '
+        })
+        .join('_;')
+
+    data.info = model.value.info
+        .filter((item) => {
+            return (!!item.name || item.name === '') && (!!item.val || item.val === '')
+        })
+        .map((item) => {
+            return item.name + '_,' + item.val
+        })
+        .join('_;')
+    data.solution = model.value.solution
+        .filter((item) => {
+            return !!item && item !== ' '
+        })
+        .join('_;')
+    console.log(data)
+
+    await http.put(data.id ? `/web/cases/save/${data.id}` : '/web/cases/save', data)
+    ElMessage.success('保存成功')
+    table.value.refresh()
+}
+function getToken(row) {
+    http.get(`/auth/admin/user/${row.id}/token`).then((res) => {
+        const { copy } = useClipboard({ legacy: true })
+        copy(res.access_token)
+        ElMessage.success('复制成功')
+    })
+}
+
+async function deleteRow(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/web/cases/del/${row.id}`)
+        ElMessage.success('删除成功')
+        table.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.form-input {
+    width: 100%;
+    display: flex;
+    .el-input + .el-input {
+        margin-left: 10px;
+    }
+    .el-input + .el-button {
+        margin-left: 20px;
+    }
+}
+.form-input + .form-input {
+    margin-top: 10px;
+}
+</style>

+ 101 - 0
src/views/web/ConnectView.vue

@@ -0,0 +1,101 @@
+<template>
+    <PagingTable url="/web/connect" :where="where" ref="table">
+        <!-- <template #filter>
+            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+        </template> -->
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="phone" label="手机号" min-width="120" />
+        <ElTableColumn prop="industry" label="行业" min-width="120" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <!-- <ElTableColumn label="操作" align="center" width="100">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+            </template>
+        </ElTableColumn> -->
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="name" label="企业名称">
+            <ElInput v-model="model.name" placeholder="请输入企业名称" />
+        </ElFormItem>
+        <ElFormItem prop="logo" label="LOGO">
+            <SingleUpload v-model="model.logo" />
+        </ElFormItem>
+        <ElFormItem prop="assistantName" label="助手名称">
+            <ElInput v-model="model.assistantName" placeholder="请输入昵称" />
+        </ElFormItem>
+        <ElFormItem prop="description" label="描述">
+            <ElInput type="textarea" v-model="model.description" placeholder="请输入描述" autosize />
+        </ElFormItem>
+        <ElFormItem prop="systemPrompt" label="系统提示词">
+            <ElInput type="textarea" v-model="model.systemPrompt" placeholder="请输入系统提示词" autosize />
+        </ElFormItem>
+        <ElFormItem prop="contextTemplate" label="上下文模版">
+            <ElInput type="textarea" v-model="model.contextTemplate" placeholder="请输入上下文模版" autosize />
+        </ElFormItem>
+        <ElFormItem prop="questionTemplate" label="问题模版">
+            <ElInput type="textarea" v-model="model.questionTemplate" placeholder="请输入问题模版" autosize />
+        </ElFormItem>
+        <ElFormItem prop="subdomain" label="子域名">
+            <ElInput v-model="model.subdomain" placeholder="请输入子域名" />
+        </ElFormItem>
+        <ElFormItem prop="customDomain" label="自定义域名">
+            <ElInput v-model="model.customDomain" placeholder="请输入自定义域名" />
+        </ElFormItem>
+        <ElFormItem prop="orgId" label="企业ID" v-if="model.roles && model.roles[0] === 'org'">
+            <ElInputNumber :controls="false" v-model="model.orgId" placeholder="请输入企业ID" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin') return {}
+    return {
+        orgId: user.value.orgId
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
+    logo: [{ required: true, message: '请上传LOGO', trigger: 'blur' }],
+    assistantName: [{ required: true, message: '请输入助手名称', trigger: 'blur' }],
+    description: [{ required: true, message: '请输入描述', trigger: 'blur' }],
+    systemPrompt: [{ required: true, message: '请输入系统提示词', trigger: 'blur' }]
+}
+const { showEditDialog, onEdit } = setupEditDialog(model)
+async function submit() {
+    await http.put(model.value.id ? `/admin/org/${model.value.id}` : '/admin/org', 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>

+ 172 - 0
src/views/web/HomeCaseView.vue

@@ -0,0 +1,172 @@
+<template>
+    <PagingTable url="/web/homeCases" :where="where" ref="table">
+        <template #filter>
+            <!-- <ElButton :icon="Plus" @click="onEdit()">添加</ElButton> -->
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="name" label="客户名称" min-width="120" />
+        <ElTableColumn prop="logo" label="LOGO" min-width="80" align="center">
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.logo" v-if="row.logo" style="width: 30px; height: 30px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="type" label="行业类型" min-width="120" />
+        <ElTableColumn prop="icon1" label="icon1" min-width="80" align="center">
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.icon1" v-if="row.icon1" style="width: 30px; height: 30px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="icon2" label="icon2" min-width="80" align="center">
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.icon2" v-if="row.icon2" style="width: 30px; height: 30px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="img" label="案例图片" min-width="80" align="center">
+            <template #default="{ row }">
+                <div class="flex items-center justify-center">
+                    <ElImage :src="row.img" v-if="row.img" style="width: 50px; height: 100px" fit="cover"></ElImage>
+                </div>
+            </template>
+        </ElTableColumn>
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" fixed="right" width="150">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+                <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="name" label="标题">
+            <ElInput v-model="model.name" placeholder="请输入标题" />
+        </ElFormItem>
+        <ElFormItem prop="logo" label="LOGO">
+            <SingleUpload v-model="model.logo" />
+        </ElFormItem>
+        <ElFormItem prop="type" label="行业">
+            <ElInput v-model="model.type" placeholder="请输入行业" />
+        </ElFormItem>
+        <ElFormItem prop="icon1" label="icon1">
+            <SingleUpload v-model="model.icon1" />
+        </ElFormItem>
+        <ElFormItem prop="icon2" label="icon2">
+            <SingleUpload v-model="model.icon2" />
+        </ElFormItem>
+        <ElFormItem prop="img" label="案例图片">
+            <SingleUpload v-model="model.img" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin')
+        return {
+            del: 0
+        }
+    return {
+        orgId: user.value.orgId,
+        del: 0
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }]
+}
+const { showEditDialog } = setupEditDialog(model)
+
+function onEdit(row) {
+    let names = []
+    model.value = {
+        ...row
+    }
+    showEditDialog.value = true
+}
+
+async function delVal(index) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        model.value.values.splice(index, 1)
+        ElMessage.success('删除成功')
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+function addVal() {
+    model.value.values.push('')
+}
+
+async function submit() {
+    let data = { ...model.value }
+
+    await http.put(data.id ? `/web/homeCases/save/${data.id}` : '/web/homeCases/save', data)
+    ElMessage.success('保存成功')
+    table.value.refresh()
+}
+function getToken(row) {
+    http.get(`/auth/admin/user/${row.id}/token`).then((res) => {
+        const { copy } = useClipboard({ legacy: true })
+        copy(res.access_token)
+        ElMessage.success('复制成功')
+    })
+}
+
+async function deleteRow(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/web/homeCases/del/${row.id}`)
+        ElMessage.success('删除成功')
+        table.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.form-input {
+    width: 100%;
+    display: flex;
+
+    .el-input + .el-button {
+        margin-left: 20px;
+    }
+}
+.form-input + .form-input {
+    margin-top: 10px;
+}
+</style>

+ 148 - 0
src/views/web/HomeWebView.vue

@@ -0,0 +1,148 @@
+<template>
+    <PagingTable url="/web/base" :where="where" :order="{ direction: 'ASC' }" ref="table">
+        <template #filter>
+            <!-- <ElButton :icon="Plus" @click="onEdit()">添加</ElButton> -->
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="name" label="标题_;副标题" min-width="120" />
+        <ElTableColumn prop="desc" label="描述" min-width="80" />
+        <ElTableColumn prop="val" label="内容" min-width="120" />
+        <ElTableColumn prop="direction" label="位置" min-width="120" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" width="100">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="name" label="标题">
+            <ElInput v-model="model.name" placeholder="请输入标题" />
+        </ElFormItem>
+        <ElFormItem prop="sub" label="副标题">
+            <ElInput v-model="model.sub" placeholder="请输入副标题" />
+        </ElFormItem>
+        <ElFormItem prop="desc" label="描述">
+            <ElInput type="textarea" v-model="model.desc" placeholder="请输入描述" autosize />
+        </ElFormItem>
+        <ElFormItem prop="val" label="内容">
+            <div class="form-input" v-for="(item, index) in model.values" :key="index">
+                <ElInput type="text" v-model="model.values[index]" placeholder="请输入内容" />
+                <el-button type="danger" size="mini" plain @click="delVal(index)">删除</el-button>
+            </div>
+            <div class="form-input">
+                <el-button type="primary" size="mini" plain @click="addVal">新增</el-button>
+            </div>
+        </ElFormItem>
+        <ElFormItem prop="direction" label="位置">
+            <ElInput v-model="model.direction" disabled />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin') return {}
+    return {
+        orgId: user.value.orgId
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
+}
+const { showEditDialog } = setupEditDialog(model)
+
+function onEdit(row) {
+    let names = []
+    if (row.name) {
+        names = row.name.split('_;')
+    }
+    model.value = {
+        ...row,
+        name: names.length > 0 ? names[0] : '',
+        sub: names.length > 1 ? names[1] : '',
+        values: row.val.split('_;')
+    }
+    showEditDialog.value = true
+}
+
+async function delVal(index) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        model.value.values.splice(index, 1)
+        ElMessage.success('删除成功')
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+function addVal() {
+    model.value.values.push('')
+}
+
+async function submit() {
+    let data = { ...model.value }
+    if (model.value.sub) {
+        data.name = model.value.name + '_;' + model.value.sub
+    }
+    data.val = model.value.values
+        .filter((item) => {
+            return !!item && item !== ' '
+        })
+        .join('_;')
+    delete data.sub
+    delete data.values
+    console.log(data)
+
+    await http.put(data.id ? `/web/base/save/${data.id}` : '/web/base/save', data)
+    ElMessage.success('保存成功')
+    table.value.refresh()
+}
+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>
+
+<style lang="less" scoped>
+.form-input {
+    width: 100%;
+    display: flex;
+
+    .el-input + .el-button {
+        margin-left: 20px;
+    }
+}
+.form-input + .form-input {
+    margin-top: 10px;
+}
+</style>

+ 117 - 0
src/views/web/QuestionView.vue

@@ -0,0 +1,117 @@
+<template>
+    <PagingTable url="/web/question" :where="where" ref="table">
+        <template #filter>
+            <!-- <ElButton :icon="Plus" @click="onEdit()">添加</ElButton> -->
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="question" label="问题" min-width="120" />
+        <ElTableColumn prop="answer" label="答案" min-width="80" />
+        <ElTableColumn label="操作" align="center" fixed="right" width="150">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+                <ElButton @click="deleteRow(row)" type="danger">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        label-width="100px"
+    >
+        <ElFormItem prop="question" label="问题">
+            <ElInput v-model="model.question" placeholder="请输入问题" autosize />
+        </ElFormItem>
+        <ElFormItem prop="answer" label="答案">
+            <ElInput type="textarea" v-model="model.answer" placeholder="请输入答案" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref, computed } 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 { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import SingleUpload from '@/components/SingleUpload.vue'
+import { storeToRefs } from 'pinia'
+import { useUserStore } from '@/stores/user'
+
+const { user } = storeToRefs(useUserStore())
+const role = computed(() => user.value?.roles[0])
+const where = computed(() => {
+    if (role.value === 'admin')
+        return {
+            del: 0
+        }
+    return {
+        orgId: user.value.orgId,
+        del: 0
+    }
+})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }]
+}
+const { showEditDialog } = setupEditDialog(model)
+
+function onEdit(row) {
+    model.value = {
+        ...row
+    }
+    showEditDialog.value = true
+}
+
+async function submit() {
+    let data = { ...model.value }
+
+    console.log(data)
+
+    await http.put(data.id ? `/web/question/save/${data.id}` : '/web/question/save', data)
+    ElMessage.success('保存成功')
+    table.value.refresh()
+}
+function getToken(row) {
+    http.get(`/auth/admin/user/${row.id}/token`).then((res) => {
+        const { copy } = useClipboard({ legacy: true })
+        copy(res.access_token)
+        ElMessage.success('复制成功')
+    })
+}
+
+async function deleteRow(row) {
+    try {
+        await ElMessageBox.confirm('此操作将永久删除数据, 是否继续?', '提示', {
+            type: 'warning'
+        })
+        await http.delete(`/web/question/del/${row.id}`)
+        ElMessage.success('删除成功')
+        table.value.refresh()
+    } catch (error) {
+        if ('cancel' !== error) ElMessage.error(error.message)
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.form-input {
+    width: 100%;
+    display: flex;
+
+    .el-input + .el-button {
+        margin-left: 20px;
+    }
+}
+.form-input + .form-input {
+    margin-top: 10px;
+}
+</style>