mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: refactor user list page for new UI
This commit is contained in:
		@@ -157,6 +157,7 @@ type ModelAPIConfig struct {
 | 
			
		||||
type SystemConfig struct {
 | 
			
		||||
	Title      string `json:"title"`
 | 
			
		||||
	AdminTitle string `json:"admin_title"`
 | 
			
		||||
	Logo       string `json:"logo"`
 | 
			
		||||
	InitPower  int    `json:"init_power"` // 新用户注册赠送算力值
 | 
			
		||||
 | 
			
		||||
	RegisterWays    []string `json:"register_ways"`    // 注册方式:支持手机,邮箱注册
 | 
			
		||||
 
 | 
			
		||||
@@ -79,12 +79,12 @@ func (h *ConfigHandler) Get(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var m map[string]interface{}
 | 
			
		||||
	err := utils.JsonDecode(config.Config, &m)
 | 
			
		||||
	var value map[string]interface{}
 | 
			
		||||
	err := utils.JsonDecode(config.Config, &value)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, m)
 | 
			
		||||
	resp.SUCCESS(c, value)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ type User struct {
 | 
			
		||||
	Nickname    string               `json:"nickname"`
 | 
			
		||||
	Avatar      string               `json:"avatar"`
 | 
			
		||||
	Salt        string               `json:"salt"`          // 密码盐
 | 
			
		||||
	Power       int                  `json:"calls"`         // 剩余算力
 | 
			
		||||
	Power       int                  `json:"power"`         // 剩余算力
 | 
			
		||||
	ChatConfig  types.UserChatConfig `json:"chat_config"`   // 聊天配置
 | 
			
		||||
	ChatRoles   []string             `json:"chat_roles"`    // 聊天角色集合
 | 
			
		||||
	ChatModels  []string             `json:"chat_models"`   // AI模型集合
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
VITE_PROXY_BASE_URL="/api"
 | 
			
		||||
VITE_TARGET_URL="http://172.22.11.2:5678"
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <link rel="icon" href="/favicon.ico">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>ChatPlus-Ai</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
    <title>极客AI助手-控制台</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<div id="app"></div>
 | 
			
		||||
<script type="module" src="/src/main.ts"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { IconDown, IconExport } from "@arco-design/web-vue/es/icon";
 | 
			
		||||
import { useAuthStore } from "@/stores/auth";
 | 
			
		||||
import {IconDown, IconExport} from "@arco-design/web-vue/es/icon";
 | 
			
		||||
import {useAuthStore} from "@/stores/auth";
 | 
			
		||||
import useState from "@/composables/useState";
 | 
			
		||||
import Logo from "/images/logo.png";
 | 
			
		||||
import avatar from "/images/user-info.jpg";
 | 
			
		||||
@@ -8,42 +8,53 @@ import donateImg from "/images/wechat-pay.png";
 | 
			
		||||
 | 
			
		||||
import SystemMenu from "./SystemMenu.vue";
 | 
			
		||||
import PageWrapper from "./PageWrapper.vue";
 | 
			
		||||
import {getConfig} from "@/views/System/api";
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
 | 
			
		||||
const logoWidth = "200px";
 | 
			
		||||
const authStore = useAuthStore();
 | 
			
		||||
const [visible, setVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
const system = ref({})
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  system.value = (await getConfig({key: "system"})).data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  reload();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <ALayout class="custom-layout">
 | 
			
		||||
    <ALayoutHeader class="custom-layout-header">
 | 
			
		||||
      <div class="logo">
 | 
			
		||||
        <img :src="Logo" alt="logo" />
 | 
			
		||||
        <span>ChatPlus 控制台</span>
 | 
			
		||||
        <img :src="Logo" alt="logo"/>
 | 
			
		||||
        <span>{{ system?.admin_title }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="action">
 | 
			
		||||
        <ADropdown>
 | 
			
		||||
          <ASpace align="center" :size="4">
 | 
			
		||||
            <a-avatar class="user-avatar" :size="30">
 | 
			
		||||
              <img :src="avatar" />
 | 
			
		||||
              <img :src="avatar"/>
 | 
			
		||||
            </a-avatar>
 | 
			
		||||
            <IconDown />
 | 
			
		||||
            <IconDown/>
 | 
			
		||||
          </ASpace>
 | 
			
		||||
          <template #content>
 | 
			
		||||
            <a
 | 
			
		||||
              class="dropdown-link"
 | 
			
		||||
              href="https://github.com/yangjian102621/chatgpt-plus"
 | 
			
		||||
              target="_blank"
 | 
			
		||||
                class="dropdown-link"
 | 
			
		||||
                href="https://github.com/yangjian102621/chatgpt-plus"
 | 
			
		||||
                target="_blank"
 | 
			
		||||
            >
 | 
			
		||||
              <ADoption value="1">
 | 
			
		||||
                <template #icon>
 | 
			
		||||
                  <icon-github />
 | 
			
		||||
                  <icon-github/>
 | 
			
		||||
                </template>
 | 
			
		||||
                <span>ChatPlus-AI 创作系统</span>
 | 
			
		||||
                <span>{{ system?.title }}</span>
 | 
			
		||||
              </ADoption>
 | 
			
		||||
            </a>
 | 
			
		||||
            <ADoption value="2" @click="setVisible(true)">
 | 
			
		||||
              <template #icon>
 | 
			
		||||
                <icon-wechatpay />
 | 
			
		||||
                <icon-wechatpay/>
 | 
			
		||||
              </template>
 | 
			
		||||
              <span>打赏作者</span>
 | 
			
		||||
            </ADoption>
 | 
			
		||||
@@ -52,7 +63,7 @@ const [visible, setVisible] = useState(false);
 | 
			
		||||
            <APopconfirm content="确认退出?" position="bl" @ok="authStore.logout">
 | 
			
		||||
              <AButton status="warning" class="logout-area">
 | 
			
		||||
                <ASpace align="center">
 | 
			
		||||
                  <IconExport size="16" />
 | 
			
		||||
                  <IconExport size="16"/>
 | 
			
		||||
                  <span>退出登录</span>
 | 
			
		||||
                </ASpace>
 | 
			
		||||
              </AButton>
 | 
			
		||||
@@ -62,26 +73,26 @@ const [visible, setVisible] = useState(false);
 | 
			
		||||
      </div>
 | 
			
		||||
    </ALayoutHeader>
 | 
			
		||||
    <ALayout>
 | 
			
		||||
      <SystemMenu :width="logoWidth" />
 | 
			
		||||
      <SystemMenu :width="logoWidth"/>
 | 
			
		||||
      <ALayoutContent>
 | 
			
		||||
        <PageWrapper>
 | 
			
		||||
          <slot />
 | 
			
		||||
          <slot/>
 | 
			
		||||
        </PageWrapper>
 | 
			
		||||
      </ALayoutContent>
 | 
			
		||||
    </ALayout>
 | 
			
		||||
  </ALayout>
 | 
			
		||||
  <a-modal
 | 
			
		||||
    v-model:visible="visible"
 | 
			
		||||
    class="donate-dialog"
 | 
			
		||||
    width="400px"
 | 
			
		||||
    title="请作者喝杯咖啡"
 | 
			
		||||
    :footer="false"
 | 
			
		||||
      v-model:visible="visible"
 | 
			
		||||
      class="donate-dialog"
 | 
			
		||||
      width="400px"
 | 
			
		||||
      title="请作者喝杯咖啡"
 | 
			
		||||
      :footer="false"
 | 
			
		||||
  >
 | 
			
		||||
    <a-alert :closable="false" :show-icon="false">
 | 
			
		||||
      如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~
 | 
			
		||||
    </a-alert>
 | 
			
		||||
    <p>
 | 
			
		||||
      <a-image :src="donateImg" />
 | 
			
		||||
      <a-image :src="donateImg"/>
 | 
			
		||||
    </p>
 | 
			
		||||
  </a-modal>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -90,23 +101,27 @@ const [visible, setVisible] = useState(false);
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  &-header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 60px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    border-bottom: 1px solid var(--color-neutral-2);
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      width: v-bind("logoWidth");
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      gap: 12px;
 | 
			
		||||
 | 
			
		||||
      img {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .action {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      padding: 0 12px;
 | 
			
		||||
@@ -116,14 +131,17 @@ const [visible, setVisible] = useState(false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-link {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.donate-dialog {
 | 
			
		||||
  p {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logout-area {
 | 
			
		||||
  padding: 8px 0;
 | 
			
		||||
  display: flex;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,34 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import {onMounted} from "vue";
 | 
			
		||||
import {Message} from "@arco-design/web-vue";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import useRequest from "@/composables/useRequest";
 | 
			
		||||
import { getConfig, modelList, save } from "./api";
 | 
			
		||||
import {getConfig, modelList, save} from "./api";
 | 
			
		||||
import SystemUploader from "./SystemUploader.vue";
 | 
			
		||||
 | 
			
		||||
const { formRef, formData: system, handleSubmit, submitting } = useSubmit({});
 | 
			
		||||
const {formRef, formData: system, handleSubmit, submitting} = useSubmit({});
 | 
			
		||||
 | 
			
		||||
const [getModelOptions, modelOptions, modelOptionsLoading] = useRequest(modelList);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  title: [{ required: true, message: "请输入网站标题" }],
 | 
			
		||||
  admin_title: [{ required: true, message: "请输入控制台标题" }],
 | 
			
		||||
  title: [{required: true, message: "请输入网站标题"}],
 | 
			
		||||
  admin_title: [{required: true, message: "请输入控制台标题"}],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  await handleSubmit(
 | 
			
		||||
    () =>
 | 
			
		||||
      save({
 | 
			
		||||
        key: "system",
 | 
			
		||||
        config: system,
 | 
			
		||||
      }),
 | 
			
		||||
    {}
 | 
			
		||||
      () =>
 | 
			
		||||
          save({
 | 
			
		||||
            key: "system",
 | 
			
		||||
            config: system,
 | 
			
		||||
          }),
 | 
			
		||||
      {}
 | 
			
		||||
  );
 | 
			
		||||
  Message.success("保存成功");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  const { data } = await getConfig({ key: "system" });
 | 
			
		||||
  const {data} = await getConfig({key: "system"});
 | 
			
		||||
  data && Object.assign(system, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -41,43 +41,56 @@ onMounted(async () => {
 | 
			
		||||
  <a-card :bordered="false">
 | 
			
		||||
    <a-form ref="formRef" :model="system" :rules="rules" auto-label-width :disabled="submitting">
 | 
			
		||||
      <a-form-item label="网站标题" field="title">
 | 
			
		||||
        <a-input v-model="system['title']" />
 | 
			
		||||
        <a-input v-model="system['title']"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="控制台标题" field="admin_title">
 | 
			
		||||
        <a-input v-model="system['admin_title']" />
 | 
			
		||||
        <a-input v-model="system['admin_title']"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="注册赠送对话次数" field="user_init_calls">
 | 
			
		||||
        <a-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数" />
 | 
			
		||||
      <a-form-item label="网站Logo" field="logo">
 | 
			
		||||
        <SystemUploader v-model="system['logo']" placeholder="推荐图片宽高比为 1:1"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="注册赠送绘图次数" field="init_img_calls">
 | 
			
		||||
        <a-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数" />
 | 
			
		||||
      <a-form-item label="注册赠送算力" field="init_power">
 | 
			
		||||
        <a-input-number v-model="system['init_power']" placeholder="新用户注册赠送初始算力"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="邀请赠送对话次数" field="invite_chat_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['invite_chat_calls']"
 | 
			
		||||
          placeholder="邀请新用户注册赠送对话次数"
 | 
			
		||||
      <a-form-item label="邀请用户赠送算力" field="invite_power">
 | 
			
		||||
        <a-input-number
 | 
			
		||||
            v-model="system['invite_power']"
 | 
			
		||||
            placeholder="邀请新用户注册赠送算力"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="邀请赠送绘图次数" field="invite_img_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['invite_img_calls']"
 | 
			
		||||
          placeholder="邀请新用户注册赠送绘图次数"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
      <a-form-item label="VIP每月赠送算力" field="vip_month_power">
 | 
			
		||||
        <a-input-number v-model="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="VIP每月对话次数" field="vip_month_calls">
 | 
			
		||||
        <a-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数" />
 | 
			
		||||
      <a-form-item label="MJ绘画价格" field="mj_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['mj_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="MidJourney 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="VIP每月绘图次数" field="vip_month_img_calls">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['vip_month_img_calls']"
 | 
			
		||||
          placeholder="VIP用户每月赠送绘图次数"
 | 
			
		||||
        />
 | 
			
		||||
      <a-form-item label="SD绘画价格" field="sd_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['sd_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="Stable-Diffusion 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="DALL绘画价格" field="dall_power">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-input-number v-model="system['dall_power']" placeholder=""/>
 | 
			
		||||
          <a-tooltip content="DALL-E-3 单次绘图消耗多少单位算力" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="开放注册" field="enabled_register">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-switch v-model="system['enabled_register']" />
 | 
			
		||||
          <a-switch v-model="system['enabled_register']"/>
 | 
			
		||||
          <a-tooltip content="关闭注册之后只能通过管理后台添加用户" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
@@ -89,61 +102,58 @@ onMounted(async () => {
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="启用众筹功能" field="enabled_reward">
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <a-switch v-model="system['enabled_reward']" />
 | 
			
		||||
          <a-tooltip content="如果关闭次功能将不在用户菜单显示众筹二维码" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
          <a-switch v-model="system['enabled_reward']"/>
 | 
			
		||||
          <a-tooltip content="开启众筹功能允许用户使用个人微信收款码进行收款" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <template v-if="system['enabled_reward']">
 | 
			
		||||
        <a-form-item label="单次对话价格" field="chat_call_price">
 | 
			
		||||
          <a-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item label="单次绘图价格" field="img_call_price">
 | 
			
		||||
          <a-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例" />
 | 
			
		||||
        <a-form-item label="众筹算力单价" field="power_price">
 | 
			
		||||
          <a-input-number v-model="system['power_price']" placeholder="单位算力价格,如1块10个单位算力,那便填写 0.1"/>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item label="收款二维码" field="reward_img">
 | 
			
		||||
          <SystemUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址" />
 | 
			
		||||
          <SystemUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址"/>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
      <a-form-item label="微信客服二维码" field="wechat_card_url">
 | 
			
		||||
        <SystemUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码" />
 | 
			
		||||
        <SystemUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码"/>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="订单超时时间" field="order_pay_timeout">
 | 
			
		||||
        <a-space style="width: 100%">
 | 
			
		||||
          <a-input
 | 
			
		||||
            v-model.number="system['order_pay_timeout']"
 | 
			
		||||
            placeholder="单位:秒"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
          <a-input-number
 | 
			
		||||
              v-model="system['order_pay_timeout']"
 | 
			
		||||
              placeholder="单位:秒"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
          />
 | 
			
		||||
          <a-tooltip position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
            <template #content> 系统会定期清理超时未支付的订单<br />默认值:900秒 </template>
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
            <template #content> 系统会定期清理超时未支付的订单<br/>默认值:900秒</template>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="会员充值说明" field="order_pay_info_text">
 | 
			
		||||
        <a-textarea
 | 
			
		||||
          v-model="system['order_pay_info_text']"
 | 
			
		||||
          :autosize="{ minRows: 3, maxRows: 10 }"
 | 
			
		||||
          placeholder="请输入会员充值说明文字,比如介绍会员计划"
 | 
			
		||||
            v-model="system['order_pay_info_text']"
 | 
			
		||||
            :autosize="{ minRows: 3, maxRows: 10 }"
 | 
			
		||||
            placeholder="请输入会员充值说明文字,比如介绍会员计划"
 | 
			
		||||
        />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="默认AI模型" field="default_models">
 | 
			
		||||
        <a-space style="width: 100%">
 | 
			
		||||
          <a-select
 | 
			
		||||
            v-model="system['default_models']"
 | 
			
		||||
            multiple
 | 
			
		||||
            :filterable="true"
 | 
			
		||||
            placeholder="选择AI模型,多选"
 | 
			
		||||
            :options="modelOptions"
 | 
			
		||||
            :loading="modelOptionsLoading"
 | 
			
		||||
            :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
            style="width: 100%"
 | 
			
		||||
              v-model="system['default_models']"
 | 
			
		||||
              multiple
 | 
			
		||||
              :filterable="true"
 | 
			
		||||
              placeholder="选择AI模型,多选"
 | 
			
		||||
              :options="modelOptions"
 | 
			
		||||
              :loading="modelOptionsLoading"
 | 
			
		||||
              :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
          >
 | 
			
		||||
          </a-select>
 | 
			
		||||
          <a-tooltip content="新用户注册默认开通的 AI 模型" position="right">
 | 
			
		||||
            <icon-info-circle-fill size="18" />
 | 
			
		||||
            <icon-info-circle-fill size="18"/>
 | 
			
		||||
          </a-tooltip>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
 | 
			
		||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
 | 
			
		||||
import { getList, save as saveApi, deletApi, resetPassword } from "./api";
 | 
			
		||||
import type {SearchTableColumns} from "@/components/SearchTable/type";
 | 
			
		||||
import {getList, save as saveApi, deletApi, resetPassword} from "./api";
 | 
			
		||||
import UserForm from "./UserForm.vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import { dateFormat } from "@gpt-vue/packages/utils";
 | 
			
		||||
import {Message} from "@arco-design/web-vue";
 | 
			
		||||
import {dateFormat} from "@gpt-vue/packages/utils";
 | 
			
		||||
import UserPassword from "./UserPassword.vue";
 | 
			
		||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
 | 
			
		||||
 | 
			
		||||
const columns: SearchTableColumns[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "账号",
 | 
			
		||||
@@ -16,21 +17,13 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "剩余对话次数",
 | 
			
		||||
    dataIndex: "calls",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "剩余绘图次数",
 | 
			
		||||
    dataIndex: "img_calls",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "累计消耗tokens",
 | 
			
		||||
    dataIndex: "total_tokens",
 | 
			
		||||
    title: "剩余算力",
 | 
			
		||||
    dataIndex: "power",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "状态",
 | 
			
		||||
    dataIndex: "status",
 | 
			
		||||
    render: ({ record }) => {
 | 
			
		||||
    render: ({record}) => {
 | 
			
		||||
      return record.status ? "正常" : "停用";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
@@ -38,15 +31,19 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
    title: "过期时间",
 | 
			
		||||
    dataIndex: "expired_time",
 | 
			
		||||
    width: 180,
 | 
			
		||||
    render: ({ record }) => {
 | 
			
		||||
      return dateFormat(record.expired_time);
 | 
			
		||||
    render: ({record}) => {
 | 
			
		||||
      if (record.expired_time > 0) {
 | 
			
		||||
        return dateFormat(record.expired_time)
 | 
			
		||||
      } else {
 | 
			
		||||
        return '长期有效'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "注册时间",
 | 
			
		||||
    dataIndex: "created_at",
 | 
			
		||||
    width: 180,
 | 
			
		||||
    render: ({ record }) => {
 | 
			
		||||
    render: ({record}) => {
 | 
			
		||||
      return dateFormat(record.created_at);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
@@ -60,13 +57,13 @@ const columns: SearchTableColumns[] = [
 | 
			
		||||
 | 
			
		||||
//弹窗
 | 
			
		||||
const editModal = useCustomFormPopup(UserForm, saveApi, {
 | 
			
		||||
  popupProps: (arg) => ({ title: arg[0].record ? "编辑用户" : "新增用户" }),
 | 
			
		||||
  popupProps: (arg) => ({title: arg[0].record ? "编辑用户" : "新增用户"}),
 | 
			
		||||
});
 | 
			
		||||
const password = useCustomFormPopup(UserPassword, resetPassword, {
 | 
			
		||||
  popupProps: (arg) => ({ title: "重置密码" }),
 | 
			
		||||
  popupProps: (arg) => ({title: "重置密码"}),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleDelete = async ({ id }: { id: string }, reload) => {
 | 
			
		||||
const handleDelete = async ({id}: { id: string }, reload) => {
 | 
			
		||||
  const res = await deletApi(id);
 | 
			
		||||
  if (res.code === 0) {
 | 
			
		||||
    Message.success("操作成功");
 | 
			
		||||
@@ -85,7 +82,10 @@ const handleDelete = async ({ id }: { id: string }, reload) => {
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #search-extra="{ reload }">
 | 
			
		||||
      <a-button @click="editModal({ reload })" size="small" type="primary">
 | 
			
		||||
        <template #icon> <icon-plus /> </template>新增
 | 
			
		||||
        <template #icon>
 | 
			
		||||
          <icon-plus/>
 | 
			
		||||
        </template>
 | 
			
		||||
        新增
 | 
			
		||||
      </a-button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </SearchTable>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,74 +1,75 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="username"
 | 
			
		||||
      label="账号"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入账号' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
        field="username"
 | 
			
		||||
        label="账号"
 | 
			
		||||
        :rules="[{ required: true, message: '请输入账号' }]"
 | 
			
		||||
        :validate-trigger="['change', 'input']"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input v-model="form.username" placeholder="请输入账号" />
 | 
			
		||||
      <a-input v-model="form.username" placeholder="请输入账号"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      v-if="!props.data.id"
 | 
			
		||||
      field="password"
 | 
			
		||||
      label="密码"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入密码' }]"
 | 
			
		||||
      :validate-trigger="['change', 'input']"
 | 
			
		||||
      showable
 | 
			
		||||
        v-if="!props.data.id"
 | 
			
		||||
        field="password"
 | 
			
		||||
        label="密码"
 | 
			
		||||
        :rules="[{ required: true, message: '请输入密码' }]"
 | 
			
		||||
        :validate-trigger="['change', 'input']"
 | 
			
		||||
        showable
 | 
			
		||||
    >
 | 
			
		||||
      <a-input v-model="form.password" placeholder="请输入密码" />
 | 
			
		||||
      <a-input v-model="form.password" placeholder="请输入密码"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="calls"
 | 
			
		||||
      label="对话次数"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入对话次数' }]"
 | 
			
		||||
        field="calls"
 | 
			
		||||
        label="对话次数"
 | 
			
		||||
        :rules="[{ required: true, message: '请输入对话次数' }]"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="form.calls" placeholder="请输入对话次数" />
 | 
			
		||||
      <a-input-number v-model="form.calls" placeholder="请输入对话次数"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      field="img_calls"
 | 
			
		||||
      label="绘图次数"
 | 
			
		||||
      :rules="[{ required: true, message: '请输入绘图次数' }]"
 | 
			
		||||
        field="img_calls"
 | 
			
		||||
        label="绘图次数"
 | 
			
		||||
        :rules="[{ required: true, message: '请输入绘图次数' }]"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
 | 
			
		||||
      <a-input-number v-model="form.img_calls" placeholder="请输入绘图次数"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="expired_time" label="有效期">
 | 
			
		||||
      <a-date-picker v-model="form.expired_time" placeholder="请选择有效期" />
 | 
			
		||||
      <a-date-picker v-model="form.expired_time" placeholder="请选择有效期"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="chat_roles" label="聊天角色">
 | 
			
		||||
      <a-select
 | 
			
		||||
        :field-names="{ value: 'key', label: 'name' }"
 | 
			
		||||
        v-model="form.chat_roles"
 | 
			
		||||
        placeholder="请选择聊天角色"
 | 
			
		||||
        multiple
 | 
			
		||||
        :options="roleOption"
 | 
			
		||||
        :rules="[{ required: true, message: '请选择聊天角色' }]"
 | 
			
		||||
          :field-names="{ value: 'key', label: 'name' }"
 | 
			
		||||
          v-model="form.chat_roles"
 | 
			
		||||
          placeholder="请选择聊天角色"
 | 
			
		||||
          multiple
 | 
			
		||||
          :options="roleOption"
 | 
			
		||||
          :rules="[{ required: true, message: '请选择聊天角色' }]"
 | 
			
		||||
      >
 | 
			
		||||
      </a-select>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="chat_models" label="模型角色">
 | 
			
		||||
      <a-select
 | 
			
		||||
        :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
        v-model="form.chat_models"
 | 
			
		||||
        placeholder="请选择模型角色"
 | 
			
		||||
        multiple
 | 
			
		||||
        :options="modalOption"
 | 
			
		||||
        :rules="[{ required: true, message: '请选择模型角色' }]"
 | 
			
		||||
          :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
          v-model="form.chat_models"
 | 
			
		||||
          placeholder="请选择模型角色"
 | 
			
		||||
          multiple
 | 
			
		||||
          :options="modalOption"
 | 
			
		||||
          :rules="[{ required: true, message: '请选择模型角色' }]"
 | 
			
		||||
      >
 | 
			
		||||
      </a-select>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="status" label="启用状态">
 | 
			
		||||
      <a-switch v-model="form.status" />
 | 
			
		||||
      <a-switch v-model="form.status"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item field="vip" label="开通VIP">
 | 
			
		||||
      <a-switch v-model="form.vip" />
 | 
			
		||||
      <a-switch v-model="form.vip"/>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, defineExpose, defineProps } from "vue";
 | 
			
		||||
import { getModel, getRole } from "./api";
 | 
			
		||||
import {ref, defineExpose, defineProps} from "vue";
 | 
			
		||||
import {getModel, getRole} from "./api";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {},
 | 
			
		||||
});
 | 
			
		||||
@@ -96,7 +97,7 @@ if (props.data?.id) {
 | 
			
		||||
const modalOption = ref([]);
 | 
			
		||||
const roleOption = ref([]);
 | 
			
		||||
const getOption = (api, container) => {
 | 
			
		||||
  api().then(({ code, data }) => {
 | 
			
		||||
  api().then(({code, data}) => {
 | 
			
		||||
    if (code === 0) {
 | 
			
		||||
      container.value = data;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user