stylus 语法换成 saas 语法全部完成

This commit is contained in:
GeekMaster
2025-08-02 10:24:10 +08:00
parent 54f8494b5c
commit 92915f7678
24 changed files with 845 additions and 790 deletions

View File

@@ -1,3 +1,5 @@
@use 'waterfall-list.scss' as *;
.page-dall {
// background-color: #282c34;
@@ -211,7 +213,6 @@
}
// 任务列表
@import 'waterfall-list.scss';
}
.no-more-data {

View File

@@ -1,3 +1,5 @@
@use 'running-job-list.scss' as *;
.page-mj {
// background-color: #282c34;
height: 100%;
@@ -323,7 +325,6 @@
.job-list-box {
// 任务列表
@import 'running-job-list.scss';
.finish-job-list {
#waterfall {

View File

@@ -1,3 +1,6 @@
@use 'waterfall-list.scss' as *;
@use 'sd-task-dialog.scss' as *;
.page-sd {
// background-color: #282c34;
@@ -212,7 +215,6 @@
}
// 任务列表
@import 'waterfall-list.scss';
}
.no-more-data {
@@ -222,8 +224,6 @@
}
}
@import 'sd-task-dialog.scss';
.mj-list-item-prompt {
.el-icon {
margin-left: 10px;

View File

@@ -1,3 +1,5 @@
@use 'sd-task-dialog.scss' as *;
@keyframes expandUp {
0% {
transform: scaleY(0);
@@ -79,7 +81,8 @@
}
.iconfont {
}
.el-icon, .iconfont {
.el-icon,
.iconfont {
top: 2px;
cursor: pointer;
color: #fff !important;
@@ -123,6 +126,4 @@
}
}
}
@use "sd-task-dialog.scss" as *;
}
}

View File

@@ -1,3 +1,5 @@
@use 'model-select.scss' as *;
.mobile-chat-list {
.content {
padding-top: 46px;
@@ -32,5 +34,3 @@
}
}
}
@use "model-select.scss" as *;

View File

@@ -1,3 +1,5 @@
@use 'model-select.scss' as *;
.mobile-chat {
.van-nav-bar {
position: static;
@@ -84,5 +86,3 @@
// }
// }
// }
@use "model-select.scss" as *;

View File

@@ -1,6 +1,6 @@
.job-list-box {
@import 'running-job-list.scss';
@use 'running-job-list.scss' as *;
.job-list-box {
.finish-job-list {
#waterfall {
display: flex;

View File

@@ -361,32 +361,8 @@
</el-main>
</el-container>
<el-dialog v-model="showNotice" :show-close="true" class="notice-dialog" title="网站公告">
<div class="notice">
<div v-html="notice"></div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="notShow" type="primary">我知道了不再显示</el-button>
</span>
</template>
</el-dialog>
<ChatSetting :show="showChatSetting" @hide="showChatSetting = false" />
<!-- <el-dialog
v-model="showConversationDialog"
title="实时语音通话"
:before-close="hangUp"
>
<realtime-conversation
@close="showConversationDialog = false"
ref="conversationRef"
:height="dialogHeight + 'px'"
/>
</el-dialog> -->
<el-dialog v-model="showConversationDialog" title="实时语音通话" :fullscreen="true">
<div v-loading="!frameLoaded">
<iframe
@@ -426,8 +402,7 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
import Clipboard from 'clipboard'
import { ElMessage, ElMessageBox } from 'element-plus'
import 'highlight.js/styles/a11y-dark.css'
import MarkdownIt from 'markdown-it'
import emoji from 'markdown-it-emoji'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { getUserToken } from '../store/session'
@@ -453,9 +428,7 @@ const isLogin = ref(false)
const showHello = ref(true)
const inputRef = ref(null)
const textHeightRef = ref(null)
const showNotice = ref(false)
const notice = ref('')
const noticeKey = ref('SYSTEM_NOTICE')
const store = useSharedStore()
const row = ref(1)
const showChatSetting = ref(false)
@@ -651,30 +624,6 @@ getSystemInfo()
ElMessage.error('获取系统配置失败:' + e.message)
})
const md = new MarkdownIt({
breaks: true,
html: true,
linkify: true,
typographer: true,
}).use(emoji)
// 获取系统公告
httpGet('/api/config/get?key=notice')
.then((res) => {
try {
notice.value = md.render(res.data['content'])
const oldNotice = localStorage.getItem(noticeKey.value)
// 如果公告有更新,则显示公告
if (oldNotice !== notice.value && notice.value.length > 10) {
showNotice.value = true
}
} catch (e) {
console.warn(e)
}
})
.catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
// 获取工具函数
httpGet('/api/function/list')
.then((res) => {
@@ -1318,11 +1267,6 @@ const getModelValue = (model_id) => {
return ''
}
const notShow = () => {
localStorage.setItem(noticeKey.value, notice.value)
showNotice.value = false
}
const files = ref([])
// 插入文件
const insertFile = (file) => {
@@ -1367,33 +1311,7 @@ const realtimeChat = () => {
<style lang="scss">
@use '@/assets/css/markdown/vue.css' as *;
.notice-dialog {
.el-dialog__header {
padding-bottom: 0;
}
.el-dialog__body {
padding: 0 20px;
h2 {
margin: 20px 0 15px 0;
}
ol,
ul {
padding-left: 10px;
}
ol {
list-style: decimal-leading-zero;
padding-left: 20px;
}
ul {
list-style: inside;
}
}
}
@use 'sass:color';
.input-container {
.el-textarea {
@@ -1452,7 +1370,7 @@ const realtimeChat = () => {
margin-left: auto;
&:hover {
color: darken(#f56c6c, 10%);
color: color.adjust(#f56c6c, $lightness: -10%);
}
}
}

View File

@@ -78,6 +78,19 @@
</div>
<footer-bar />
<!-- 网站公告对话框 -->
<el-dialog v-model="showNotice" :show-close="true" class="notice-dialog" title="网站公告">
<div class="notice">
<div v-html="notice"></div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="notShow" type="primary">我知道了不再显示</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@@ -88,6 +101,8 @@ import { checkSession, getLicenseInfo, getSystemInfo } from '@/store/cache'
import { removeUserToken } from '@/store/session'
import { httpGet } from '@/utils/http'
import { ElMessage } from 'element-plus'
import MarkdownIt from 'markdown-it'
import emoji from 'markdown-it-emoji'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
@@ -103,6 +118,19 @@ const githubURL = ref(import.meta.env.VITE_GITHUB_URL)
const giteeURL = ref(import.meta.env.VITE_GITEE_URL)
const navs = ref([])
// 公告相关变量
const showNotice = ref(false)
const notice = ref('')
const noticeKey = ref('SYSTEM_NOTICE')
// Markdown 解析器
const md = new MarkdownIt({
breaks: true,
html: true,
linkify: true,
typographer: true,
}).use(emoji)
onMounted(() => {
getSystemInfo()
.then((res) => {
@@ -135,14 +163,68 @@ onMounted(() => {
isLogin.value = true
})
.catch(() => {})
// 获取系统公告
httpGet('/api/config/get?key=notice')
.then((res) => {
try {
notice.value = md.render(res.data['content'])
const oldNotice = localStorage.getItem(noticeKey.value)
// 如果公告有更新,则显示公告
if (oldNotice !== notice.value && notice.value.length > 10) {
showNotice.value = true
}
} catch (e) {
console.warn(e)
}
})
.catch((e) => {
ElMessage.error('获取系统配置失败:' + e.message)
})
})
const logout = () => {
removeUserToken()
router.push('/login')
}
// 不再显示公告
const notShow = () => {
localStorage.setItem(noticeKey.value, notice.value)
showNotice.value = false
}
</script>
<style lang="scss" scoped>
@use '../assets/css/index.scss' as *;
</style>
<style lang="scss">
.notice-dialog {
.el-dialog__header {
padding-bottom: 0;
}
.el-dialog__body {
padding: 0 20px;
h2 {
margin: 20px 0 15px 0;
}
ol,
ul {
padding-left: 10px;
}
ol {
list-style: decimal-leading-zero;
padding-left: 20px;
}
ul {
list-style: inside;
}
}
}
</style>

View File

@@ -529,7 +529,7 @@
</div>
<!-- 视频预览对话框 -->
<el-dialog v-model="store.showDialog" title="视频预览" width="70%" center>
<el-dialog v-model="store.showDialog" title="视频预览" center>
<video
:src="store.currentVideoUrl"
autoplay="true"

View File

@@ -291,36 +291,36 @@ const changePreset = (row) => {
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.list {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 150px;
margin-right 10px;
max-width: 150px;
margin-right: 10px;
}
}
.copy-key {
margin-left 5px
cursor pointer
margin-left: 5px;
cursor: pointer;
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
padding 20px 0
display flex
justify-content righ
padding: 20px 0;
display: flex;
justify-content: right;
}
}
.el-form {
.el-form-item__content {
.info {
color #999999
color: #999999;
}
.el-icon {
padding-left: 10px;

View File

@@ -5,7 +5,7 @@
</div>
<el-row>
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
<el-table :data="items" :row-key="(row) => row.id" table-layout="auto">
<el-table-column type="selection" width="38"></el-table-column>
<el-table-column prop="name" label="分类名称">
<template #default="scope">
@@ -17,13 +17,17 @@
</el-table-column>
<el-table-column label="图标" prop="icon">
<template #default="scope">
<el-image v-if="scope.row.icon" :src="scope.row.icon" style="width: 45px; height: 45px; border-radius: 50%"/>
<el-image
v-if="scope.row.icon"
:src="scope.row.icon"
style="width: 45px; height: 45px; border-radius: 50%"
/>
<el-tag type="info" v-else>无图标</el-tag>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enabled']" @change="enableSet(scope.row)"/>
<el-switch v-model="scope.row['enabled']" @change="enableSet(scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" width="180">
@@ -40,23 +44,19 @@
</el-row>
<el-dialog
v-model="showDialog"
:title="title"
:close-on-click-modal="false"
style="width: 90%; max-width: 600px;"
v-model="showDialog"
:title="title"
:close-on-click-modal="false"
style="width: 90%; max-width: 600px"
>
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
<el-form-item label="分类名称" prop="name">
<el-input v-model="item.name" autocomplete="off"/>
<el-input v-model="item.name" autocomplete="off" />
</el-form-item>
<el-form-item label="应用图标" prop="icon">
<el-input v-model="item.icon">
<template #append>
<el-upload
:auto-upload="true"
:show-file-list="false"
:http-request="uploadImg"
>
<el-upload :auto-upload="true" :show-file-list="false" :http-request="uploadImg">
上传
</el-upload>
</template>
@@ -78,34 +78,37 @@
</template>
<script setup>
import {onMounted, onUnmounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {removeArrayItem} from "@/utils/libs";
import {Sortable} from "sortablejs";
import Compressor from "compressorjs";
import { httpGet, httpPost } from '@/utils/http'
import { removeArrayItem } from '@/utils/libs'
import { Plus } from '@element-plus/icons-vue'
import Compressor from 'compressorjs'
import { ElMessage } from 'element-plus'
import { Sortable } from 'sortablejs'
import { onMounted, reactive, ref } from 'vue'
// 变量定义
const items = ref([])
const item = ref({})
const showDialog = ref(false)
const title = ref("")
const title = ref('')
const rules = reactive({
name: [{required: true, message: '请输入分类名称', trigger: 'change',}],
name: [{ required: true, message: '请输入分类名称', trigger: 'change' }],
})
const loading = ref(true)
const formRef = ref(null)
// 获取数据
const fetchData = () => {
httpGet('/api/admin/app/type/list').then((res) => {
if (res.data) {
items.value = res.data
}
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
httpGet('/api/admin/app/type/list')
.then((res) => {
if (res.data) {
items.value = res.data
}
loading.value = false
})
.catch(() => {
ElMessage.error('获取数据失败')
})
}
onMounted(() => {
@@ -116,12 +119,14 @@ onMounted(() => {
Sortable.create(drawBodyWrapper, {
sort: true,
animation: 500,
onEnd({newIndex, oldIndex, from}) {
onEnd({ newIndex, oldIndex, from }) {
if (oldIndex === newIndex) {
return
}
const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
const sortedData = Array.from(from.children).map((row) =>
row.querySelector('.sort').getAttribute('data-id')
)
const ids = []
const sorts = []
sortedData.forEach((id, index) => {
@@ -130,22 +135,23 @@ onMounted(() => {
items.value[index].sort_num = index + 1
})
httpPost("/api/admin/app/type/sort", {ids: ids, sorts: sorts}).then(() => {
}).catch(e => {
ElMessage.error("排序失败" + e.message)
})
}
httpPost('/api/admin/app/type/sort', { ids: ids, sorts: sorts })
.then(() => {})
.catch((e) => {
ElMessage.error('排序失败:' + e.message)
})
},
})
})
const add = function () {
title.value = "新增分类"
title.value = '新增分类'
showDialog.value = true
item.value = { enabled: true, }
item.value = { enabled: true }
}
const edit = function (row) {
title.value = "修改分类"
title.value = '修改分类'
showDialog.value = true
item.value = row
}
@@ -157,12 +163,14 @@ const save = function () {
}
if (valid) {
showDialog.value = false
httpPost('/api/admin/app/type/save', item.value).then(() => {
ElMessage.success('操作成功!')
fetchData()
}).catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
httpPost('/api/admin/app/type/save', item.value)
.then(() => {
ElMessage.success('操作成功!')
fetchData()
})
.catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
} else {
return false
}
@@ -171,23 +179,26 @@ const save = function () {
// 设置启用状态
const enableSet = (row) => {
httpPost('/api/admin/app/type/enable', {id: row.id, enabled: row.enabled}).then(() => {
ElMessage.success("操作成功")
}).catch(e => {
ElMessage.error("操作失败" + e.message)
})
httpPost('/api/admin/app/type/enable', { id: row.id, enabled: row.enabled })
.then(() => {
ElMessage.success('操作成功!')
})
.catch((e) => {
ElMessage.error('操作失败:' + e.message)
})
}
// 删除数据
const remove = function (row) {
httpGet('/api/admin/app/type/remove?id=' + row.id).then(() => {
ElMessage.success("删除成功")
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
httpGet('/api/admin/app/type/remove?id=' + row.id)
.then(() => {
ElMessage.success('删除成功!')
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
})
})
.catch((e) => {
ElMessage.error('删除失败:' + e.message)
})
}).catch((e) => {
ElMessage.error("删除失败" + e.message)
})
}
// 图片上传
@@ -196,19 +207,21 @@ const uploadImg = (file) => {
new Compressor(file.file, {
quality: 0.6,
success(result) {
const formData = new FormData();
formData.append('file', result, result.name);
const formData = new FormData()
formData.append('file', result, result.name)
// 执行上传操作
httpPost('/api/admin/upload', formData).then((res) => {
item.value.icon = res.data.url
ElMessage.success('上传成功')
}).catch((e) => {
ElMessage.error('上传失败:' + e.message)
})
httpPost('/api/admin/upload', formData)
.then((res) => {
item.value.icon = res.data.url
ElMessage.success('上传成功')
})
.catch((e) => {
ElMessage.error('上传失败:' + e.message)
})
},
error(e) {
ElMessage.error('上传失败:' + e.message)
},
});
};
</script>
})
}
</script>

View File

@@ -392,22 +392,21 @@ const remove = function (row) {
}
</script>
<style lang="stylus" scoped>
@use "../../assets/css/admin/form.scss" as *;
<style lang="scss" scoped>
@use '../../assets/css/admin/form.scss' as *;
.model-list {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 150px;
margin-right 10px;
max-width: 150px;
margin-right: 10px;
}
}
.cell {
.copy-model {
margin-left 6px
cursor pointer
margin-left: 6px;
cursor: pointer;
}
}
@@ -419,22 +418,21 @@ const remove = function (row) {
}
.el-select {
width: 100%
width: 100%;
}
.sort {
cursor move
cursor: move;
.iconfont {
position relative
top 1px
position: relative;
top: 1px;
}
}
.pagination {
padding 20px 0
display flex
justify-content right
padding: 20px 0;
display: flex;
justify-content: right;
}
}
</style>

View File

@@ -10,17 +10,19 @@
<span class="sort" :data-id="scope.row.id">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="函数别名" prop="label"/>
<el-table-column label="功能描述" prop="description"/>
<el-table-column label="函数别名" prop="label" />
<el-table-column label="功能描述" prop="description" />
<el-table-column label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="functionSet('enabled',scope.row)"/>
<el-switch v-model="scope.row.enabled" @change="functionSet('enabled', scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="right">
<template #default="scope">
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)"
>编辑</el-button
>
<el-popconfirm title="确定要删除当前函数吗?" @confirm="remove(scope.row)" :width="200">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
@@ -31,34 +33,18 @@
</el-table>
</el-row>
<el-dialog
v-model="showDialog"
:title="title"
:close-on-click-modal="false"
width="50%"
>
<el-dialog v-model="showDialog" :title="title" :close-on-click-modal="false" width="50%">
<el-form :model="item" label-width="120px" ref="formRef" label-position="left" :rules="rules">
<el-form-item label="函数名称:" prop="name">
<el-input
v-model="item.name"
placeholder="函数名称最好为英文"
autocomplete="off"
/>
<el-input v-model="item.name" placeholder="函数名称最好为英文" autocomplete="off" />
</el-form-item>
<el-form-item label="函数标签:" prop="label">
<el-input
v-model="item.label"
placeholder="函数的中文名称"
autocomplete="off"
/>
<el-input v-model="item.label" placeholder="函数的中文名称" autocomplete="off" />
</el-form-item>
<el-form-item label="功能描述:" prop="description">
<el-input
v-model="item.description"
autocomplete="off"
/>
<el-input v-model="item.description" autocomplete="off" />
</el-form-item>
<el-form-item label="函数参数:" prop="parameters">
@@ -66,10 +52,7 @@
<el-table :data="params" :border="childBorder" size="small">
<el-table-column label="参数名称" width="120">
<template #default="scope">
<el-input
v-model="scope.row.name"
autocomplete="off"
/>
<el-input v-model="scope.row.name" autocomplete="off" />
</template>
</el-table-column>
<el-table-column label="参数类型" width="120">
@@ -81,17 +64,14 @@
</el-table-column>
<el-table-column label="参数描述">
<template #default="scope">
<el-input
v-model="scope.row.desc"
autocomplete="off"
/>
<el-input v-model="scope.row.desc" autocomplete="off" />
</template>
</el-table-column>
<el-table-column label="必填参数" width="80">
<template #default="scope">
<div class="param-opt">
<el-checkbox v-model="scope.row.required"/>
<el-checkbox v-model="scope.row.required" />
</div>
</template>
</el-table-column>
@@ -99,7 +79,13 @@
<el-table-column label="操作" width="80">
<template #default="scope">
<div class="param-opt">
<el-button type="danger" :icon="Delete" circle @click="removeParam(scope.$index)" size="small"/>
<el-button
type="danger"
:icon="Delete"
circle
@click="removeParam(scope.$index)"
size="small"
/>
</div>
</template>
</el-table-column>
@@ -108,7 +94,7 @@
<div class="param-line">
<el-button type="primary" @click="addParam" size="small">
<el-icon>
<Plus/>
<Plus />
</el-icon>
增加参数
</el-button>
@@ -118,25 +104,21 @@
<el-form-item label="API 地址:" prop="action">
<el-input
v-model="item.action"
autocomplete="off"
placeholder="该函数实现的API地址可以是第三方服务API"
v-model="item.action"
autocomplete="off"
placeholder="该函数实现的API地址可以是第三方服务API"
/>
</el-form-item>
<el-form-item label="API Token" prop="token">
<el-input
v-model="item.token"
autocomplete="off"
placeholder="API授权Token"
>
<el-input v-model="item.token" autocomplete="off" placeholder="API授权Token">
<template #append>
<el-tooltip
class="box-item"
effect="dark"
content="只有本地服务才可以使用自动生成Token<br/>第三方服务请填写第三方服务API Token"
placement="top-end"
raw-content
class="box-item"
effect="dark"
content="只有本地服务才可以使用自动生成Token<br/>第三方服务请填写第三方服务API Token"
placement="top-end"
raw-content
>
<el-button @click="generateToken">生成Token</el-button>
</el-tooltip>
@@ -144,27 +126,26 @@
</el-input>
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="item.enabled"/>
<el-switch v-model="item.enabled" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</span>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {Delete, Plus} from "@element-plus/icons-vue";
import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {arrayContains, copyObj} from "@/utils/libs";
import { httpGet, httpPost } from '@/utils/http'
import { arrayContains, copyObj } from '@/utils/libs'
import { Delete, Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
const showDialog = ref(false)
const parentBorder = ref(true)
@@ -174,35 +155,36 @@ const item = ref({})
const params = ref([])
const formRef = ref(null)
const loading = ref(true)
const title = ref("新增函数")
const title = ref('新增函数')
const rules = reactive({
name: [{required: true, message: '请输入函数名称', trigger: 'blur',}],
label: [{required: true, message: '请输入函数标签', trigger: 'blur',}],
description: [{required: true, message: '请输入函数功能描述', trigger: 'blur',}],
name: [{ required: true, message: '请输入函数名称', trigger: 'blur' }],
label: [{ required: true, message: '请输入函数标签', trigger: 'blur' }],
description: [{ required: true, message: '请输入函数功能描述', trigger: 'blur' }],
})
const paramsType = ref(["string", "number"])
const paramsType = ref(['string', 'number'])
onMounted(() => {
fetch()
})
const fetch = () => {
httpGet('/api/admin/function/list').then((res) => {
if (res.data) {
tableData.value = res.data
}
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
httpGet('/api/admin/function/list')
.then((res) => {
if (res.data) {
tableData.value = res.data
}
loading.value = false
})
.catch(() => {
ElMessage.error('获取数据失败')
})
}
// 编辑
const curIndex = ref(0)
const rowEdit = function (index, row) {
title.value = "编辑函数"
title.value = '编辑函数'
curIndex.value = index
item.value = copyObj(row)
// initialize parameters
@@ -214,7 +196,7 @@ const rowEdit = function (index, row) {
name: key,
type: props[key].type,
desc: props[key].description,
required: arrayContains(required, key)
required: arrayContains(required, key),
})
}
params.value = _params
@@ -222,7 +204,7 @@ const rowEdit = function (index, row) {
}
const addRow = function () {
item.value = {enabled: true}
item.value = { enabled: true }
params.value = []
showDialog.value = true
}
@@ -235,104 +217,114 @@ const save = function () {
const required = []
// process params
for (let i = 0; i < params.value.length; i++) {
properties[params.value[i].name] = {"type": params.value[i].type, "description": params.value[i].desc}
properties[params.value[i].name] = {
type: params.value[i].type,
description: params.value[i].desc,
}
if (params.value[i].required) {
required.push(params.value[i].name)
}
}
item.value.parameters = {type: "object", "properties": properties, required: required}
httpPost('/api/admin/function/save', item.value).then((res) => {
ElMessage.success('操作成功')
console.log(res.data)
if (item.value.id > 0) {
tableData.value[curIndex.value] = item.value
} else {
tableData.value.push(res.data)
}
}).catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
item.value.parameters = { type: 'object', properties: properties, required: required }
httpPost('/api/admin/function/save', item.value)
.then((res) => {
ElMessage.success('操作成功')
console.log(res.data)
if (item.value.id > 0) {
tableData.value[curIndex.value] = item.value
} else {
tableData.value.push(res.data)
}
})
.catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
}
})
}
const remove = function (row) {
httpGet('/api/admin/function/remove?id=' + row.id).then(() => {
ElMessage.success("删除成功!")
fetch()
}).catch(() => {
ElMessage.error("删除失败!")
})
httpGet('/api/admin/function/remove?id=' + row.id)
.then(() => {
ElMessage.success('删除成功!')
fetch()
})
.catch(() => {
ElMessage.error('删除失败!')
})
}
const addParam = function () {
if (!params.value) {
item.value = []
}
params.value.push({name: "", type: "string", desc: "", required: false})
params.value.push({ name: '', type: 'string', desc: '', required: false })
}
const removeParam = function (index) {
params.value.splice(index, 1);
params.value.splice(index, 1)
}
const functionSet = (filed, row) => {
httpPost('/api/admin/function/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
ElMessage.success("操作成功!")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
httpPost('/api/admin/function/set', { id: row.id, filed: filed, value: row[filed] })
.then(() => {
ElMessage.success('操作成功!')
})
.catch((e) => {
ElMessage.error('操作失败:' + e.message)
})
}
const generateToken = () => {
httpGet('/api/admin/function/token').then(res => {
ElMessage.success("生成 Token 成功")
item.value.token = res.data
}).catch(() => {
ElMessage.error("生成 Token 失败")
})
httpGet('/api/admin/function/token')
.then((res) => {
ElMessage.success('生成 Token 成功')
item.value.token = res.data
})
.catch(() => {
ElMessage.error('生成 Token 失败')
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.function {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
}
.param-line {
padding 5px 0
padding: 5px 0;
.el-icon {
margin-right 5px;
margin-right: 5px;
}
}
.param-opt {
display flex
justify-content center
display: flex;
justify-content: center;
.el-icon {
font-size: 20px;
margin-top 5px;
margin-left 5px;
cursor pointer
margin-top: 5px;
margin-left: 5px;
cursor: pointer;
}
}
.el-input--small {
width 30px;
width: 30px;
.el-input__inner {
text-align center
text-align: center;
}
}
.pagination {
padding 20px 0
display flex
justify-content right
padding: 20px 0;
display: flex;
justify-content: right;
}
}
</style>
</style>

View File

@@ -52,6 +52,6 @@ watch(
)
</script>
<style lang="stylus" scoped>
@use "../../assets/css/main.scss" as *;
<style lang="scss" scoped>
@use '../../assets/css/main.scss' as *;
</style>

View File

@@ -123,107 +123,108 @@ const doLogin = function (verifyData) {
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.bg {
position absolute
left 0
right 0
top 0
bottom 0
background #8d4bbb
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #8d4bbb;
// background-image url("~@/assets/img/transparent-bg.png")
// background-repeat:repeat;
background-image url("@/assets/img/admin-login-bg.jpg")
background-size cover
background-position center
background-repeat no-repeat
background-image: url('@/assets/img/admin-login-bg.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
z-index 0
z-index: 0;
}
.admin-login {
position absolute
left 0
top 0
z-index 10
display flex
justify-content center
width: 100%
height: 100vh
position: absolute;
left: 0;
top: 0;
z-index: 10;
display: flex;
justify-content: center;
width: 100%;
height: 100vh;
.main {
max-width 400px
display flex
justify-content center
align-items center
height 100vh
max-width: 400px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.contain {
width 100%
padding 40px;
color #ffffff
border-radius 10px;
background rgba(0, 0, 0, 0.4)
width: 100%;
padding: 40px;
color: #ffffff;
border-radius: 10px;
background: rgba(0, 0, 0, 0.4);
.logo {
text-align center
text-align: center;
.el-image {
width 120px;
cursor pointer
border-radius 50%
width: 120px;
cursor: pointer;
border-radius: 50%;
}
}
.header {
width 100%
width: 100%;
//margin-bottom 20px
padding 10px
font-size 26px
text-align center
padding: 10px;
font-size: 26px;
text-align: center;
}
.content {
width 100%
height: auto
border-radius 3px
width: 100%;
height: auto;
border-radius: 3px;
.el-input {
margin 10px 0
margin: 10px 0;
}
.block {
margin-bottom 16px
margin-bottom: 16px;
.el-input__inner {
border 1px solid $gray-v6 !important
border: 1px solid #dcdfe6 !important;
.el-icon-user, .el-icon-lock {
font-size 20px
.el-icon-user,
.el-icon-lock {
font-size: 20px;
}
}
}
.btn-row {
padding-top 10px;
padding-top: 10px;
.login-btn {
width 100%
font-size 16px
letter-spacing 2px
width: 100%;
font-size: 16px;
letter-spacing: 2px;
}
}
.text-line {
justify-content center
padding-top 10px;
font-size 14px;
justify-content: center;
padding-top: 10px;
font-size: 14px;
}
}
}
.foot-container {
background rgba(0, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.3);
--text-color: #ffffff;
}
}

View File

@@ -63,22 +63,20 @@ const fetchList = function (_page, _pageSize) {
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.list {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
margin-top 20px
display flex
justify-content center
margin-top: 20px;
display: flex;
justify-content: center;
}
}
</style>

View File

@@ -1,14 +1,13 @@
<template>
<div class="container list" v-loading="loading">
<div class="handle-box">
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
</div>
<el-row>
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
<el-table-column prop="username" label="用户名"/>
<el-table-column prop="last_login_ip" label="最后登录IP"/>
<el-table :data="items" :row-key="(row) => row.id" table-layout="auto">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="last_login_ip" label="最后登录IP" />
<el-table-column label="最后登录时间">
<template #default="scope">
@@ -17,7 +16,7 @@
</el-table-column>
<el-table-column prop="enabled" label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['status']" @change="enable(scope.row)"/>
<el-switch v-model="scope.row['status']" @change="enable(scope.row)" />
</template>
</el-table-column>
<el-table-column label="创建时间">
@@ -28,7 +27,9 @@
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" type="primary" @click="resetPass(scope.row)">重置密码</el-button>
<el-button size="small" type="primary" @click="resetPass(scope.row)"
>重置密码</el-button
>
<el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)" :width="200">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
@@ -39,64 +40,62 @@
</el-table>
</el-row>
<el-dialog
v-model="showDialog"
title="添加用户"
:close-on-click-modal="false"
>
<el-dialog v-model="showDialog" title="添加用户" :close-on-click-modal="false">
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
<el-form-item label="用户名" prop="username">
<el-input v-model="item.username" autocomplete="off"/>
<el-input v-model="item.username" autocomplete="off" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="item.password" type="password" autocomplete="off"/>
<el-input v-model="item.password" type="password" autocomplete="off" />
</el-form-item>
<el-form-item label="重复密码" prop="repass">
<el-input v-model="item.repass" type="password" autocomplete="off"/>
<el-input v-model="item.repass" type="password" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">提交</el-button>
</span>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">提交</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox} from "element-plus";
import {dateFormat, removeArrayItem} from "@/utils/libs";
import {Plus} from "@element-plus/icons-vue";
import { httpGet, httpPost } from '@/utils/http'
import { dateFormat, removeArrayItem } from '@/utils/libs'
import { Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
// 变量定义
const items = ref([])
const item = ref({})
const showDialog = ref(false)
const title = ref("")
const title = ref('')
const loading = ref(true)
const formRef = ref(null)
const rules = reactive({
username: [{required: true, message: '请输入用户名', trigger: 'change',}],
password: [{required: true, message: '请输入密码', trigger: 'change',}],
repass: [{required: true, message: '请再次输入密码', trigger: 'change',}],
username: [{ required: true, message: '请输入用户名', trigger: 'change' }],
password: [{ required: true, message: '请输入密码', trigger: 'change' }],
repass: [{ required: true, message: '请再次输入密码', trigger: 'change' }],
})
// 获取数据
const fetchData = () => {
httpGet('/api/admin/list').then((res) => {
items.value = res.data
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
httpGet('/api/admin/list')
.then((res) => {
items.value = res.data
loading.value = false
})
.catch(() => {
ElMessage.error('获取数据失败')
})
}
onMounted(() => {
@@ -113,33 +112,37 @@ const resetPass = function (row) {
ElMessageBox.prompt('请输入新密码', '重置密码', {
confirmButtonText: '确认',
cancelButtonText: '取消',
}).then(({value}) => {
httpPost("/api/admin/resetPass", {
id: row.id,
password: value
}).then(() => {
ElMessage.success("操作成功")
}).catch(e => {
ElMessage.error("操作失败" + e.message)
})
}).catch(() => {
})
.then(({ value }) => {
httpPost('/api/admin/resetPass', {
id: row.id,
password: value,
})
.then(() => {
ElMessage.success('操作成功')
})
.catch((e) => {
ElMessage.error('操作失败:' + e.message)
})
})
.catch(() => {})
}
const save = function () {
formRef.value.validate((valid) => {
if (item.value.password !== item.value.repass) {
return ElMessage.error("两次输入密码不一致")
return ElMessage.error('两次输入密码不一致')
}
if (valid) {
showDialog.value = false
httpPost('/api/admin/save', item.value).then((res) => {
ElMessage.success('操作成功!')
fetchData()
}).catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
httpPost('/api/admin/save', item.value)
.then((res) => {
ElMessage.success('操作成功!')
fetchData()
})
.catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
} else {
return false
}
@@ -147,41 +150,43 @@ const save = function () {
}
const enable = (row) => {
httpPost('/api/admin/enable', {id: row.id, enabled: row.status}).then(() => {
ElMessage.success("操作成功")
}).catch(e => {
ElMessage.error("操作失败" + e.message)
})
httpPost('/api/admin/enable', { id: row.id, enabled: row.status })
.then(() => {
ElMessage.success('操作成功!')
})
.catch((e) => {
ElMessage.error('操作失败:' + e.message)
})
}
const remove = function (row) {
httpGet('/api/admin/remove?id=' + row.id).then(() => {
ElMessage.success("删除成功")
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
httpGet('/api/admin/remove?id=' + row.id)
.then(() => {
ElMessage.success('删除成功!')
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
})
})
.catch((e) => {
ElMessage.error('删除失败:' + e.message)
})
}).catch((e) => {
ElMessage.error("删除失败" + e.message)
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.list {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
padding 20px 0
display flex
justify-content right
padding: 20px 0;
display: flex;
justify-content: right;
}
}
</style>
</style>

View File

@@ -252,29 +252,28 @@ const uploadImg = (file) => {
}
</script>
<style lang="stylus" scoped>
@use "../../assets/css/admin/form.scss" as *;
@use "../../assets/css/main.scss" as *;
<style lang="scss" scoped>
@use '../../assets/css/admin/form.scss' as *;
@use '../../assets/css/main.scss' as *;
.menu {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
}
.menu-icon {
width 36px
height 36px
width: 36px;
height: 36px;
}
.sort {
cursor move
cursor: move;
.iconfont {
position relative
top 1px
position: relative;
top: 1px;
}
}
.el-select {
width: 100%
width: 100%;
}
.el-table .cell {

View File

@@ -3,7 +3,12 @@
<div class="handle-box">
<el-input v-model="query.order_no" placeholder="订单号" class="handle-input mr10"></el-input>
<el-select v-model="query.status" placeholder="订单状态" style="width: 100px">
<el-option v-for="item in orderStatus" :key="item.value" :label="item.label" :value="item.value" />
<el-option
v-for="item in orderStatus"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-date-picker
v-model="query.pay_time"
@@ -33,13 +38,13 @@
<el-table-column label="下单时间">
<template #default="scope">
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
<span>{{ dateFormat(scope.row['created_at']) }}</span>
</template>
</el-table-column>
<el-table-column label="支付时间">
<template #default="scope">
<span v-if="scope.row['pay_time']">{{ dateFormat(scope.row["pay_time"]) }}</span>
<span v-if="scope.row['pay_time']">{{ dateFormat(scope.row['pay_time']) }}</span>
<el-tag v-else>未支付</el-tag>
</template>
</el-table-column>
@@ -74,90 +79,89 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { httpGet, httpPost } from "@/utils/http";
import { ElMessage, ElMessageBox } from "element-plus";
import { dateFormat, removeArrayItem } from "@/utils/libs";
import { Delete, Search } from "@element-plus/icons-vue";
import { httpGet, httpPost } from '@/utils/http'
import { dateFormat, removeArrayItem } from '@/utils/libs'
import { Delete, Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref } from 'vue'
// 变量定义
const items = ref([]);
const query = ref({ order_no: "", pay_time: [], status: -1 });
const total = ref(0);
const page = ref(1);
const pageSize = ref(15);
const loading = ref(true);
const items = ref([])
const query = ref({ order_no: '', pay_time: [], status: -1 })
const total = ref(0)
const page = ref(1)
const pageSize = ref(15)
const loading = ref(true)
const orderStatus = ref([
{ value: -1, label: "全部" },
{ value: 0, label: "未支付" },
{ value: 2, label: "已支付" },
]);
{ value: -1, label: '全部' },
{ value: 0, label: '未支付' },
{ value: 2, label: '已支付' },
])
onMounted(() => {
fetchData();
});
fetchData()
})
// 获取数据
const fetchData = () => {
query.value.page = page.value;
query.value.page_size = pageSize.value;
httpPost("/api/admin/order/list", query.value)
query.value.page = page.value
query.value.page_size = pageSize.value
httpPost('/api/admin/order/list', query.value)
.then((res) => {
if (res.data) {
items.value = res.data.items;
total.value = res.data.total;
page.value = res.data.page;
pageSize.value = res.data.page_size;
items.value = res.data.items
total.value = res.data.total
page.value = res.data.page
pageSize.value = res.data.page_size
}
loading.value = false;
loading.value = false
})
.catch((e) => {
ElMessage.error("获取数据失败" + e.message);
});
};
ElMessage.error('获取数据失败' + e.message)
})
}
const remove = function (row) {
httpGet("/api/admin/order/remove?id=" + row.id)
httpGet('/api/admin/order/remove?id=' + row.id)
.then(() => {
ElMessage.success("删除成功");
ElMessage.success('删除成功')
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id;
});
return v1.id === v2.id
})
})
.catch((e) => {
ElMessage.error("删除失败" + e.message);
});
};
ElMessage.error('删除失败' + e.message)
})
}
const clearOrders = () => {
ElMessageBox.confirm("此操作将会删除所有未支付订单继续操作吗?", "删除提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
ElMessageBox.confirm('此操作将会删除所有未支付订单,继续操作吗?', '删除提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
httpGet("/api/admin/order/clear").then(() => {
ElMessage.success("订单删除成功");
page.value = 0;
fetchData();
});
});
};
httpGet('/api/admin/order/clear').then(() => {
ElMessage.success('订单删除成功')
page.value = 0
fetchData()
})
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.order {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 150px;
margin-right 10px;
max-width: 150px;
margin-right: 10px;
}
}
.opt-box {
padding-bottom: 10px;
display flex;
justify-content flex-end
display: flex;
justify-content: flex-end;
.el-icon {
margin-right: 5px;
@@ -165,14 +169,13 @@ const clearOrders = () => {
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
margin-top: 20px;
display flex
justify-content right
display: flex;
justify-content: right;
}
}
</style>

View File

@@ -1,9 +1,27 @@
<template>
<div class="container power-log" v-loading="loading">
<div class="handle-box">
<el-input v-model="query.model" placeholder="模型" class="handle-input mr10" clearable style="--el-input-height: 32px" />
<el-input v-model="query.username" placeholder="用户名" class="handle-input mr10" clearable style="--el-input-height: 32px" />
<el-input v-model.number="query.userid" placeholder="用户ID" class="handle-input mr10" clearable style="--el-input-height: 32px" />
<el-input
v-model="query.model"
placeholder="模型"
class="handle-input mr10"
clearable
style="--el-input-height: 32px"
/>
<el-input
v-model="query.username"
placeholder="用户名"
class="handle-input mr10"
clearable
style="--el-input-height: 32px"
/>
<el-input
v-model.number="query.userid"
placeholder="用户ID"
class="handle-input mr10"
clearable
style="--el-input-height: 32px"
/>
<el-select v-model="query.type" placeholder="类别" style="width: 100px">
<el-option label="全部" :value="0" />
<el-option label="充值" :value="1" />
@@ -46,7 +64,7 @@
<el-table-column prop="balance" label="余额" />
<el-table-column label="发生时间">
<template #default="scope">
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
<span>{{ dateFormat(scope.row['created_at']) }}</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
@@ -70,86 +88,86 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { httpPost } from "@/utils/http";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/libs";
import { Search } from "@element-plus/icons-vue";
import Clipboard from "clipboard";
import { httpPost } from '@/utils/http'
import { dateFormat } from '@/utils/libs'
import { Search } from '@element-plus/icons-vue'
import Clipboard from 'clipboard'
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue'
const items = ref([]);
const total = ref(0);
const page = ref(1);
const pageSize = ref(20);
const loading = ref(false);
const items = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const loading = ref(false)
const query = ref({
model: "",
model: '',
date: [],
type: 0,
page: 1,
page_size: 20,
});
const totalPower = ref(0);
})
const totalPower = ref(0)
const tagColors = ref(["", "success", "primary", "danger", "info", "warning"]);
const tagColors = ref(['', 'success', 'primary', 'danger', 'info', 'warning'])
onMounted(() => {
fetchData();
const clipboard = new Clipboard(".copy-order-no");
clipboard.on("success", () => {
ElMessage.success("复制成功");
});
fetchData()
const clipboard = new Clipboard('.copy-order-no')
clipboard.on('success', () => {
ElMessage.success('复制成功')
})
clipboard.on("error", () => {
ElMessage.error("复制失败");
});
});
clipboard.on('error', () => {
ElMessage.error('复制失败')
})
})
// 搜索
const search = () => {
page.value = 1;
fetchData();
};
page.value = 1
fetchData()
}
// 获取数据
const fetchData = () => {
loading.value = true;
query.value.page = page.value;
query.value.page_size = pageSize.value;
httpPost("/api/admin/powerLog/list", query.value)
loading.value = true
query.value.page = page.value
query.value.page_size = pageSize.value
httpPost('/api/admin/powerLog/list', query.value)
.then((res) => {
const data = res.data.data;
const data = res.data.data
if (data) {
items.value = data.items;
total.value = data.total;
page.value = data.page;
pageSize.value = data.page_size;
items.value = data.items
total.value = data.total
page.value = data.page
pageSize.value = data.page_size
}
totalPower.value = res.data.stat;
loading.value = false;
totalPower.value = res.data.stat
loading.value = false
})
.catch((e) => {
loading.value = false;
ElMessage.error("获取数据失败" + e.message);
});
};
loading.value = false
ElMessage.error('获取数据失败' + e.message)
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.power-log {
.handle-box {
--el-input-height: 32px;
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 120px;
margin-right 10px;
max-width: 120px;
margin-right: 10px;
}
}
.opt-box {
padding-bottom: 10px;
display flex;
justify-content flex-start
display: flex;
justify-content: flex-start;
.el-icon {
margin-right: 5px;
@@ -157,14 +175,13 @@ const fetchData = () => {
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
margin-top: 20px;
display flex;
display: flex;
justify-content: right;
}
}
</style>

View File

@@ -1,12 +1,11 @@
<template>
<div class="container product" v-loading="loading">
<div class="handle-box">
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
</div>
<el-row>
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
<el-table :data="items" :row-key="(row) => row.id" table-layout="auto">
<el-table-column prop="name" label="产品名称">
<template #default="scope">
<span class="sort" :data-id="scope.row.id">
@@ -15,19 +14,19 @@
</span>
</template>
</el-table-column>
<el-table-column prop="price" label="商品原价"/>
<el-table-column prop="discount" label="优惠价"/>
<el-table-column prop="price" label="商品原价" />
<el-table-column prop="discount" label="优惠价" />
<el-table-column prop="days" label="有效期()">
<template #default="scope">
<el-tag v-if="scope.row.days === 0">长期有效</el-tag>
<span v-else>{{ scope.row.days }}</span>
</template>
</el-table-column>
<el-table-column prop="power" label="算力"/>
<el-table-column prop="sales" label="销量"/>
<el-table-column prop="power" label="算力" />
<el-table-column prop="sales" label="销量" />
<el-table-column prop="enabled" label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/>
<el-switch v-model="scope.row['enabled']" @change="enable(scope.row)" />
</template>
</el-table-column>
@@ -50,83 +49,81 @@
</el-table>
</el-row>
<el-dialog
v-model="showDialog"
:title="title"
:close-on-click-modal="false"
>
<el-dialog v-model="showDialog" :title="title" :close-on-click-modal="false">
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
<el-form-item label="商品名称" prop="name">
<el-input v-model="item.name" autocomplete="off"/>
<el-input v-model="item.name" autocomplete="off" />
</el-form-item>
<el-form-item label="商品原价" prop="price">
<el-input v-model="item.price" autocomplete="off"/>
<el-input v-model="item.price" autocomplete="off" />
</el-form-item>
<el-form-item label="优惠价" prop="discount">
<el-input v-model="item.discount" autocomplete="off"/>
<el-input v-model="item.discount" autocomplete="off" />
</el-form-item>
<el-form-item label="有效期" prop="days">
<el-input v-model.number="item.days" autocomplete="off" placeholder="会员有效期()"/>
<el-input v-model.number="item.days" autocomplete="off" placeholder="会员有效期()" />
</el-form-item>
<el-form-item label="算力" prop="power">
<el-input v-model.number="item.power" autocomplete="off" placeholder="增加算力值"/>
<el-input v-model.number="item.power" autocomplete="off" placeholder="增加算力值" />
</el-form-item>
<el-form-item label="启用状态" prop="enable">
<el-switch v-model="item.enabled"/>
<el-switch v-model="item.enabled" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">提交</el-button>
</span>
<span class="dialog-footer">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="save">提交</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem} from "@/utils/libs";
import {Plus} from "@element-plus/icons-vue";
import {Sortable} from "sortablejs";
import { httpGet, httpPost } from '@/utils/http'
import { dateFormat, removeArrayItem } from '@/utils/libs'
import { Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { Sortable } from 'sortablejs'
import { onMounted, reactive, ref } from 'vue'
// 变量定义
const items = ref([])
const item = ref({})
const showDialog = ref(false)
const title = ref("")
const title = ref('')
const rules = reactive({
name: [{required: true, message: '请输入产品名称', trigger: 'change',}],
price: [{required: true, message: '请输产品价格', trigger: 'change',}],
discount: [{required: true, message: '请输优惠金额', trigger: 'change',}],
days: [{required: true, message: '请输入有效期', trigger: 'change',}],
name: [{ required: true, message: '请输入产品名称', trigger: 'change' }],
price: [{ required: true, message: '请输产品价格', trigger: 'change' }],
discount: [{ required: true, message: '请输优惠金额', trigger: 'change' }],
days: [{ required: true, message: '请输入有效期', trigger: 'change' }],
})
const loading = ref(true)
const formRef = ref(null)
// 获取数据
httpGet('/api/admin/product/list').then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
httpGet('/api/admin/product/list')
.then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
}
items.value = arr
}
items.value = arr
}
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
loading.value = false
})
.catch(() => {
ElMessage.error('获取数据失败')
})
onMounted(() => {
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
@@ -135,12 +132,14 @@ onMounted(() => {
Sortable.create(drawBodyWrapper, {
sort: true,
animation: 500,
onEnd({newIndex, oldIndex, from}) {
onEnd({ newIndex, oldIndex, from }) {
if (oldIndex === newIndex) {
return
}
const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
const sortedData = Array.from(from.children).map((row) =>
row.querySelector('.sort').getAttribute('data-id')
)
const ids = []
const sorts = []
sortedData.forEach((id, index) => {
@@ -149,21 +148,21 @@ onMounted(() => {
items.value[index].sort_num = index + 1
})
httpPost("/api/admin/product/sort", {ids: ids, sorts: sorts}).catch(e => {
ElMessage.error("排序失败" + e.message)
httpPost('/api/admin/product/sort', { ids: ids, sorts: sorts }).catch((e) => {
ElMessage.error('排序失败' + e.message)
})
}
},
})
})
const add = function () {
title.value = "新增产品"
title.value = '新增产品'
showDialog.value = true
item.value = {}
}
const edit = function (row) {
title.value = "修改产品"
title.value = '修改产品'
showDialog.value = true
item.value = row
}
@@ -174,15 +173,17 @@ const save = function () {
showDialog.value = false
item.value['price'] = parseFloat(item.value['price'])
item.value['discount'] = parseFloat(item.value['discount'])
httpPost('/api/admin/product/save', item.value).then((res) => {
ElMessage.success('操作成功!')
if (!item.value['id']) {
const newItem = res.data
items.value.push(newItem)
}
}).catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
httpPost('/api/admin/product/save', item.value)
.then((res) => {
ElMessage.success('操作成功!')
if (!item.value['id']) {
const newItem = res.data
items.value.push(newItem)
}
})
.catch((e) => {
ElMessage.error('操作失败,' + e.message)
})
} else {
return false
}
@@ -190,40 +191,44 @@ const save = function () {
}
const enable = (row) => {
httpPost('/api/admin/product/enable', {id: row.id, enabled: row.enabled}).then(() => {
ElMessage.success("操作成功")
}).catch(e => {
ElMessage.error("操作失败" + e.message)
})
httpPost('/api/admin/product/enable', { id: row.id, enabled: row.enabled })
.then(() => {
ElMessage.success('操作成功!')
})
.catch((e) => {
ElMessage.error('操作失败:' + e.message)
})
}
const remove = function (row) {
httpGet('/api/admin/product/remove?id=' + row.id).then(() => {
ElMessage.success("删除成功")
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
httpGet('/api/admin/product/remove?id=' + row.id)
.then(() => {
ElMessage.success('删除成功!')
items.value = removeArrayItem(items.value, row, (v1, v2) => {
return v1.id === v2.id
})
})
.catch((e) => {
ElMessage.error('删除失败:' + e.message)
})
}).catch((e) => {
ElMessage.error("删除失败" + e.message)
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.product {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 150px;
margin-right 10px;
max-width: 150px;
margin-right: 10px;
}
}
.opt-box {
padding-bottom: 10px;
display flex;
justify-content flex-end
display: flex;
justify-content: flex-end;
.el-icon {
margin-right: 5px;
@@ -231,22 +236,21 @@ const remove = function (row) {
}
.sort {
cursor move
cursor: move;
.iconfont {
position relative
top 1px
position: relative;
top: 1px;
}
}
.el-select {
width: 100%
width: 100%;
}
.pagination {
padding 20px 0
display flex
justify-content right
padding: 20px 0;
display: flex;
justify-content: right;
}
}
</style>
</style>

View File

@@ -2,16 +2,33 @@
<div class="container list" v-loading="loading">
<div class="handle-box">
<el-input v-model="query.code" placeholder="兑换码" class="handle-input mr10"></el-input>
<el-select v-model="query.status" placeholder="核销状态" style="width: 100px" class="handle-input mr10">
<el-option v-for="item in redeemStatus" :key="item.value" :label="item.label" :value="item.value" />
<el-select
v-model="query.status"
placeholder="核销状态"
style="width: 100px"
class="handle-input mr10"
>
<el-option
v-for="item in redeemStatus"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button type="primary" :icon="Search" @click="fetchData">搜索</el-button>
<el-button type="success" :icon="Plus" @click="add">添加兑换码</el-button>
<el-button type="primary" @click="exportItems" :loading="exporting"><i class="iconfont icon-export mr-1"></i> 导出 </el-button>
<el-button type="primary" @click="exportItems" :loading="exporting"
><i class="iconfont icon-export mr-1"></i> 导出
</el-button>
</div>
<el-row>
<el-table :data="items" :row-key="(row) => row.id" @selection-change="handleSelectionChange" table-layout="auto">
<el-table
:data="items"
:row-key="(row) => row.id"
@selection-change="handleSelectionChange"
table-layout="auto"
>
<el-table-column type="selection" width="38"></el-table-column>
<el-table-column prop="name" label="名称" />
<el-table-column prop="code" label="兑换码">
@@ -24,7 +41,7 @@
</el-table-column>
<el-table-column label="兑换人">
<template #default="scope">
<span v-if="scope.row['username'] !== ''">{{ scope.row["username"] }}</span>
<span v-if="scope.row['username'] !== ''">{{ scope.row['username'] }}</span>
<el-tag v-else>未兑换</el-tag>
</template>
</el-table-column>
@@ -32,20 +49,26 @@
<el-table-column label="生成时间">
<template #default="scope">
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
<span>{{ dateFormat(scope.row['created_at']) }}</span>
</template>
</el-table-column>
<el-table-column label="兑换时间">
<template #default="scope">
<span v-if="scope.row['redeemed_at'] > 0">{{ dateFormat(scope.row["redeemed_at"]) }}</span>
<span v-if="scope.row['redeemed_at'] > 0">{{
dateFormat(scope.row['redeemed_at'])
}}</span>
<el-tag v-else>未兑换</el-tag>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enabled']" @change="set('enabled', scope.row)" :disabled="scope.row['redeemed_at'] > 0" />
<el-switch
v-model="scope.row['enabled']"
@change="set('enabled', scope.row)"
:disabled="scope.row['redeemed_at'] > 0"
/>
</template>
</el-table-column>
@@ -102,160 +125,160 @@
</template>
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { httpGet, httpPost, httpPostDownload } from "@/utils/http";
import { ElMessage } from "element-plus";
import { dateFormat, substr, UUID } from "@/utils/libs";
import { DocumentCopy, Plus, Search } from "@element-plus/icons-vue";
import { showMessageError } from "@/utils/dialog";
import ClipboardJS from "clipboard";
import { showMessageError } from '@/utils/dialog'
import { httpGet, httpPost, httpPostDownload } from '@/utils/http'
import { dateFormat, substr, UUID } from '@/utils/libs'
import { DocumentCopy, Plus, Search } from '@element-plus/icons-vue'
import ClipboardJS from 'clipboard'
import { ElMessage } from 'element-plus'
import { onMounted, onUnmounted, ref } from 'vue'
// 变量定义
const items = ref([]);
const loading = ref(true);
const query = ref({ code: "", status: -1 });
const items = ref([])
const loading = ref(true)
const query = ref({ code: '', status: -1 })
const redeemStatus = ref([
{ value: -1, label: "全部" },
{ value: 0, label: "未核销" },
{ value: 1, label: "已核销" },
]);
const showDialog = ref(false);
const dialogLoading = ref(false);
const item = ref({ name: "", power: 0, num: 1 });
const itemIds = ref([]);
const exporting = ref(false);
{ value: -1, label: '全部' },
{ value: 0, label: '未核销' },
{ value: 1, label: '已核销' },
])
const showDialog = ref(false)
const dialogLoading = ref(false)
const item = ref({ name: '', power: 0, num: 1 })
const itemIds = ref([])
const exporting = ref(false)
const clipboard = ref(null);
const clipboard = ref(null)
onMounted(() => {
clipboard.value = new ClipboardJS(".copy-code");
clipboard.value.on("success", () => {
ElMessage.success("复制成功");
});
clipboard.value = new ClipboardJS('.copy-code')
clipboard.value.on('success', () => {
ElMessage.success('复制成功')
})
clipboard.value.on("error", () => {
ElMessage.error("复制失败");
});
clipboard.value.on('error', () => {
ElMessage.error('复制失败')
})
fetchData();
});
fetchData()
})
onUnmounted(() => {
clipboard.value.destroy();
});
clipboard.value.destroy()
})
const add = () => {
item.value = { name: "100算力点卡", power: 100, num: 1 };
showDialog.value = true;
dialogLoading.value = false;
};
item.value = { name: '100算力点卡', power: 100, num: 1 }
showDialog.value = true
dialogLoading.value = false
}
const save = () => {
if (item.value.name === "") {
return showMessageError("请输入兑换码名称");
if (item.value.name === '') {
return showMessageError('请输入兑换码名称')
}
if (item.value.power === 0) {
return showMessageError("请输入算力额度");
return showMessageError('请输入算力额度')
}
if (item.value.num <= 0) {
return showMessageError("请输入生成数量");
return showMessageError('请输入生成数量')
}
dialogLoading.value = true;
httpPost("/api/admin/redeem/create", item.value)
dialogLoading.value = true
httpPost('/api/admin/redeem/create', item.value)
.then((res) => {
ElMessage.success(`成功生成了${res.data.counter}个兑换码`);
showDialog.value = false;
fetchData();
ElMessage.success(`成功生成了${res.data.counter}个兑换码`)
showDialog.value = false
fetchData()
})
.catch((e) => {
ElMessage.error("生成失败" + e.message);
});
};
ElMessage.error('生成失败' + e.message)
})
}
const set = (filed, row) => {
httpPost("/api/admin/redeem/set", { id: row.id, filed: filed, value: row[filed] })
httpPost('/api/admin/redeem/set', { id: row.id, filed: filed, value: row[filed] })
.then(() => {
ElMessage.success("操作成功");
ElMessage.success('操作成功')
})
.catch((e) => {
ElMessage.error("操作失败" + e.message);
});
};
ElMessage.error('操作失败' + e.message)
})
}
const page = ref(1);
const pageSize = ref(12);
const total = ref(0);
const page = ref(1)
const pageSize = ref(12)
const total = ref(0)
const fetchData = () => {
query.value.page = page.value;
query.value.page_size = pageSize.value;
httpGet("/api/admin/redeem/list", query.value)
query.value.page = page.value
query.value.page_size = pageSize.value
httpGet('/api/admin/redeem/list', query.value)
.then((res) => {
if (res.data) {
items.value = res.data.items;
total.value = res.data.total;
page.value = res.data.page;
pageSize.value = res.data.page_size;
items.value = res.data.items
total.value = res.data.total
page.value = res.data.page
pageSize.value = res.data.page_size
}
loading.value = false;
loading.value = false
})
.catch((e) => {
ElMessage.error("获取数据失败" + e.message);
});
};
ElMessage.error('获取数据失败' + e.message)
})
}
const remove = function (row) {
httpGet("/api/admin/redeem/remove?id=" + row.id)
httpGet('/api/admin/redeem/remove?id=' + row.id)
.then(() => {
ElMessage.success("删除成功");
fetchData();
ElMessage.success('删除成功')
fetchData()
})
.catch((e) => {
ElMessage.error("删除失败" + e.message);
});
};
ElMessage.error('删除失败' + e.message)
})
}
const handleSelectionChange = (items) => {
itemIds.value = items.map((item) => item.id);
};
itemIds.value = items.map((item) => item.id)
}
const exportItems = () => {
query.value.ids = itemIds.value;
exporting.value = true;
httpPostDownload("/api/admin/redeem/export", query.value)
query.value.ids = itemIds.value
exporting.value = true
httpPostDownload('/api/admin/redeem/export', query.value)
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", UUID() + ".csv"); // 设置下载文件的名称
document.body.appendChild(link);
link.click();
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', UUID() + '.csv') // 设置下载文件的名称
document.body.appendChild(link)
link.click()
// 移除 <a> 标签
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
exporting.value = false;
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
exporting.value = false
})
.catch(() => {
exporting.value = false;
showMessageError("下载失败");
});
};
exporting.value = false
showMessageError('下载失败')
})
}
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
.list {
.handle-box {
margin-bottom 20px
margin-bottom: 20px;
.handle-input {
max-width 150px;
margin-right 10px;
max-width: 150px;
margin-right: 10px;
}
}
.opt-box {
padding-bottom: 10px;
display flex;
justify-content flex-end
display: flex;
justify-content: flex-end;
.el-icon {
margin-right: 5px;
@@ -263,22 +286,21 @@ const exportItems = () => {
}
.el-select {
width: 100%
width: 100%;
}
.copy-code {
cursor pointer
margin-left 6px
position relative
top 2px
font-size 14px
cursor: pointer;
margin-left: 6px;
position: relative;
top: 2px;
font-size: 14px;
}
.pagination {
margin-top: 20px;
display flex;
display: flex;
justify-content: right;
}
}
</style>

View File

@@ -842,17 +842,17 @@ const onUploadImg = (files, callback) => {
// }
</script>
<style lang="stylus" scoped>
<style lang="scss" scoped>
@use '../../assets/css/admin/form.scss' as *;
@use '../../assets/css/main.scss' as *;
.system-config {
display flex
justify-content center
display: flex;
justify-content: center;
.sys-tabs {
width 100%
background-color var(--el-bg-color)
padding 10px 20px 40px 20px
width: 100%;
background-color: var(--el-bg-color);
padding: 10px 20px 40px 20px;
//border: 1px solid var(--el-border-color);
}
}