Browse Source

新增:manger页面及产品与服务下拉菜单

master
科技小王子 9 months ago
parent
commit
ef0c041f98
  1. 2
      api/system/company/model/index.ts
  2. 2
      api/system/user/model/index.ts
  3. 1
      assets/svg/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70 (1).svg
  4. 4
      components/AppHeader.vue
  5. 1
      components/Breadcrumb.vue
  6. 10
      composables/useServerRequest.ts
  7. 24
      layouts/default.vue
  8. 120
      pages/console/index.vue
  9. 353
      pages/manage/index.vue
  10. 183
      pages/passport/login.vue
  11. 208
      pages/search/index.vue
  12. 2
      utils/common.ts

2
api/system/company/model/index.ts

@ -1,4 +1,4 @@
import { PageParam } from '@/api';
import type { PageParam } from '@/api';
/** /**
* *

2
api/system/user/model/index.ts

@ -109,6 +109,8 @@ export interface User {
truename?: string; truename?: string;
// 是否管理员:1是;2否 // 是否管理员:1是;2否
isAdmin?: boolean; isAdmin?: boolean;
// 是否同意注册协议
isAgree?: boolean;
// 客户端ID // 客户端ID
clientId?: string; clientId?: string;
// 注册来源客户端 (APP、H5、小程序等) // 注册来源客户端 (APP、H5、小程序等)

1
assets/svg/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70 (1).svg

@ -0,0 +1 @@
<svg width="70" height="70" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="17.621%" y1="50%" x2="100%" y2="50%" id="a"><stop stop-color="#FFE2B8" offset="0%"/><stop stop-color="#FFCA7C" offset="100%"/></linearGradient><filter x="-10.7%" y="-10.7%" width="121.4%" height="121.4%" filterUnits="objectBoundingBox" id="c"><feGaussianBlur stdDeviation="6" in="SourceAlpha" result="shadowBlurInner1"/><feOffset dx="3" in="shadowBlurInner1" result="shadowOffsetInner1"/><feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowInnerInner1"/></filter><path id="b" d="m1291 377 70 70v-70z"/></defs><g transform="translate(-1291 -377)" fill="none" fill-rule="evenodd"><use fill="url(#a)" xlink:href="#b"/><use fill="#000" filter="url(#c)" xlink:href="#b"/></g></svg>

After

Width:  |  Height:  |  Size: 953 B

4
components/AppHeader.vue

@ -66,7 +66,8 @@
v-model="searchValue" v-model="searchValue"
@keyup.enter.native="handleSearch" @keyup.enter.native="handleSearch"
/> />
<el-button v-if="token" @click="openSpmUrl(`https://${website.tenantId}.websoft.top/token-login`,website,website.websiteId)">控制台</el-button>
<el-button v-if="token" @click="navigateTo(`/manage`)">控制台</el-button>
<!-- <el-button v-if="token" @click="openSpmUrl(`https://${TID_ADMIN}.websoft.top/token-login`,website,Number(TID_ADMIN))">控制台</el-button>-->
<ClientOnly> <ClientOnly>
<template v-if="token"> <template v-if="token">
<el-dropdown @command="handleCommand"> <el-dropdown @command="handleCommand">
@ -120,6 +121,7 @@
const navigations = useMenu(); const navigations = useMenu();
const config = useConfigInfo(); const config = useConfigInfo();
const affix = useProductAffix(); const affix = useProductAffix();
const TID_ADMIN = localStorage.getItem('TID_ADMIN');
// //
const visibleNumber = ref<number>(6); const visibleNumber = ref<number>(6);

1
components/Breadcrumb.vue

@ -4,6 +4,7 @@
<el-breadcrumb-item :to="{ path: '/' }"><el-icon class="cursor-pointer"><ElIconHouse /></el-icon></el-breadcrumb-item> <el-breadcrumb-item :to="{ path: '/' }"><el-icon class="cursor-pointer"><ElIconHouse /></el-icon></el-breadcrumb-item>
<el-breadcrumb-item v-if="data?.parentName" :to="{ path: data.parentPath }">{{ data.parentName }}</el-breadcrumb-item> <el-breadcrumb-item v-if="data?.parentName" :to="{ path: data.parentPath }">{{ data.parentName }}</el-breadcrumb-item>
<el-breadcrumb-item v-if="data?.categoryName">{{ title ? title : data.categoryName }}</el-breadcrumb-item> <el-breadcrumb-item v-if="data?.categoryName">{{ title ? title : data.categoryName }}</el-breadcrumb-item>
<el-breadcrumb-item v-if="title">{{ title }}</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
</div> </div>
</template> </template>

10
composables/useServerRequest.ts

@ -15,12 +15,11 @@ export const useServerRequest = <T>(url: string, opts?: UseFetchOptions<T, unkno
const baseUrl = ref(''); const baseUrl = ref('');
// 获取 Cookie // 获取 Cookie
const token = useCookie('token'); const token = useCookie('token');
console.log(token.value);
if(localStorage.getItem('token')){ if(localStorage.getItem('token')){
const tokenStr = localStorage.getItem('token'); const tokenStr = localStorage.getItem('token');
if(tokenStr){ if(tokenStr){
token.value = JSON.parse(tokenStr) token.value = JSON.parse(tokenStr)
console.log(token.value,'tokenStr')
} }
} }
baseUrl.value = runtimeConfig.public.apiBase; baseUrl.value = runtimeConfig.public.apiBase;
@ -45,9 +44,10 @@ export const useServerRequest = <T>(url: string, opts?: UseFetchOptions<T, unkno
options.headers.tenantid = `${subDomain}`; options.headers.tenantid = `${subDomain}`;
} }
// TODO 2 从绑定域名解构的租户ID // TODO 2 从绑定域名解构的租户ID
if(localStorage.getItem('TID_DOMAIN')){
options.headers.tenantid = `${localStorage.getItem('TID_DOMAIN')}`;
}
options.headers.tenantid = `5`;
// if(localStorage.getItem('TID_DOMAIN')){
// options.headers.tenantid = `${localStorage.getItem('TID_DOMAIN')}`;
// }
}, },
onResponse({ response }) { onResponse({ response }) {
if (+response.status === 0 && +response._data.code !== 0) { if (+response.status === 0 && +response._data.code !== 0) {

24
layouts/default.vue

@ -2,10 +2,17 @@
<!-- 加载应用 --> <!-- 加载应用 -->
<template v-if="!loading"> <template v-if="!loading">
<!-- 管理中心界面 -->
<template v-if="getPath().startsWith('/manage')">
<slot />
</template>
<!-- 默认布局 -->
<template v-else>
<app-header /> <app-header />
<slot /> <slot />
<app-footer /> <app-footer />
</template> </template>
</template>
<!-- 异常状态 --> <!-- 异常状态 -->
<UnderMaintenance v-if="loading" /> <UnderMaintenance v-if="loading" />
@ -49,6 +56,7 @@
// TODO 1 ,ID // TODO 1 ,ID
const domain = window.location.hostname; const domain = window.location.hostname;
if(domain !== 'localhost'){ if(domain !== 'localhost'){
// //
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
@ -57,15 +65,15 @@
}}); }});
const data = domainInfo.value?.data; const data = domainInfo.value?.data;
if (data) { if (data) {
localStorage.setItem('TID_DOMAIN',`${data?.tenantId}`)
// localStorage.setItem('TID_DOMAIN',`${data?.tenantId}`)
}else { }else {
website.value.statusIcon = 'error';
website.value.statusName = '未开通';
website.value.statusUrl = 'https://websoft.top'
website.value.statusBtnText = '去开通';
website.value.statusText = '请在后台开通产品并绑定域名';
loading.value = true;
return ;
// website.value.statusIcon = 'error';
// website.value.statusName = '';
// website.value.statusUrl = 'https://websoft.top'
// website.value.statusBtnText = '';
// website.value.statusText = '';
// loading.value = true;
// return ;
} }
} }

120
pages/console/index.vue

@ -1,120 +0,0 @@
<template>
<div class="login-layout mt-[100px] m-auto sm:w-screen-xl w-full">
<div class="mt-[100px] m-auto flex sm:flex-row flex-col sm:p-0 p-3">
<el-card shadow="hover" class="sm:w-[250px] sm:flex sm:mb-0 mb-5 justify-center w-full">
<div class="flex justify-center pb-4 flex-col justify-center items-center">
<el-avatar :src="userInfo?.avatar" :size="70" />
<text class="text-gray-400 py-1 text-sm">更换头像</text>
</div>
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" />
</el-card>
<el-card shadow="hover" class="flash bg-white hover:shadow w-full sm:ml-6 ml-0">
<template #header>账号信息</template>
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:px-6 sm:py-2">
<el-form-item label="手机号码">
<el-input disabled v-model="form.mobile" />
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickname" />
</el-form-item>
<el-form-item label="邮箱账号">
<el-input v-model="form.email" placeholder="邮箱账号" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio value="1"></el-radio>
<el-radio value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="个人签名">
<el-input v-model="form.comments" type="textarea" placeholder="个人签名" :rows="4" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo, useToken, useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
import { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "~/components/UserMenu.vue";
//
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const userInfo = ref<User>();
const activeIndex = ref('');
//
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true,
isAdmin: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
assignFields(response.value?.data);
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path,'=>Path')
reload();
},
{ immediate: true }
);
</script>
<style lang="less">
body{
background: url("https://oss.wsdns.cn/20240328/797a8e323bba4fc6827abf9d8b98ba45.png");
background-size: 100%;
}
</style>

353
pages/manage/index.vue

@ -0,0 +1,353 @@
<template>
<div class="common-layout">
<el-container>
<el-header class="bg-black flex items-center justify-between" height="50px">
<el-space class="flex items-center" @click="navigateTo('/manage', { replace: false })">
<div class="expand flex items-center justify-center block bg-gray-800 hover:bg-gray-700 h-[32px] w-[32px] cursor-pointer" @mouseover="visible = true"><el-icon color="white" class="block" size="20"><Expand /></el-icon></div>
<el-avatar src="https://oss.wsdns.cn/20240331/7f7f7f57e12c45338beb7bfc7ecccfe9.png" class="cursor-pointer" shape="square" :size="32" />
<div class="user-center text-white text-lg cursor-pointer">控制台</div>
</el-space>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<template v-else>
<el-button v-if="config.showLoginButton" circle :icon="ElIconUserFilled" @click="goLogin"></el-button>
</template>
</el-header>
<el-container>
<el-aside width="200px" class="bg-white" :style="{ minHeight: 'calc(100vh - 60px)' }">
<div class="flex items-center justify-between py-4 px-5 text-lg font-bold text-center bg-white border-b-1 border-b-gray-100 border-b-solid">
企业官网 WDS
<el-icon><Memo /></el-icon>
</div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border: none"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="1">
<span>概况</span>
</el-menu-item>
<el-menu-item index="2">
<span>栏目管理</span>
</el-menu-item>
<el-menu-item index="3">
<span>文章列表</span>
</el-menu-item>
<el-menu-item index="4">
<span>素材管理</span>
</el-menu-item>
<el-menu-item index="5">
<span>广告管理</span>
</el-menu-item>
<el-menu-item index="6">
<span>订单管理</span>
</el-menu-item>
<el-menu-item index="7">
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="8">
<span>系统设置</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="overflow-y-hidden relative">
<div class="absolute inset-0 bg-black opacity-10 z-1" v-if="visible" @click="visible = false"></div>
<div v-if="visible" class="drawer w-screen-lg h-full bg-white left-0 top-[50px] fixed z-2 shadow-lg">
<div class="flex items-center justify-between p-3 border-b border-b-solid border-b-gray-200">
<div class="text-lg font-bold">产品与服务</div>
<el-space class="flex items-center">
<el-input
class="w-20"
placeholder="站内搜索"
:suffix-icon="ElIconSearch"
v-model="form.comments"
@keyup.enter.native="handleSearch"
/>
<!-- <el-input v-model="search" placeholder="搜索应用" class="w-[200px]" />-->
<div class="cursor-pointer mt-1 ml-5" @click="visible = false"><el-icon size="24" color="gray"><Close /></el-icon></div>
</el-space>
</div>
<div class="menu-content flex">
<div class="w-[200px] bg-gray-50">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border-right: 1px solid #f9fafb"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="2">
<span>精选</span>
</el-menu-item>
<el-menu-item index="3">
<span>网页</span>
</el-menu-item>
<el-menu-item index="4">
<span>移动</span>
</el-menu-item>
<el-menu-item index="5">
<span>办公</span>
</el-menu-item>
<el-menu-item index="6">
<span>其他</span>
</el-menu-item>
</el-menu>
</div>
<div class="w-full p-3">
<el-scrollbar class="w-full" height="calc(100vh - 108px)">
<el-row :gutter="16">
<template v-for="item in 20" :key="item">
<el-col :span="8">
<div class="px-3 py-1 mb-2 text-sm text-gray-700 font-bold flex justify-between items-center">API 与工具</div>
<p class="scrollbar-demo-item hover:bg-gray-100 px-3 py-1 mb-2 cursor-pointer text-sm text-gray-500 flex justify-between items-center">
<span class="product-name">{{ item }} 云服务器 ECS</span>
<span class="icon flex items-center hover:flex">
<el-icon color="gray"><Star /></el-icon>
</span>
</p>
</el-col>
</template>
</el-row>
</el-scrollbar>
</div>
</div>
</div>
<div class="my-3">
<el-alert type="warning">
<template #title>
<text type="text" class="cursor-pointer">关于小程序技术平台服务协议更新公告</text>
</template>
</el-alert>
</div>
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow rounded-lg">
<template #header>我的应用</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 bg-white mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<el-avatar :src="item?.logo" shape="square" />
<div class="info ml-2">
<div class="app-item-title font-bold flex items-center">{{ item.tenantName }}<el-tag type="warning" class="ml-2" size="small">网页</el-tag></div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
<!-- <el-card shadow="hover" class="mt-4">-->
<!-- <el-tabs v-model="activeIndex" type="">-->
<!-- <el-tab-pane label="微信小程序" name="mp-weixin">-->
<!-- <el-table :data="tableData" stripe style="width: 100%">-->
<!-- <el-table-column prop="date" label="Date" width="180" />-->
<!-- <el-table-column prop="name" label="Name" width="180" />-->
<!-- <el-table-column prop="address" label="Address" />-->
<!-- </el-table>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="网页/移动应用" name="web">-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </el-card>-->
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow ">
<template #header>开发工具推荐</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<div class="info ml-2">
<div class="app-item-title">{{ item.tenantName }}</div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo, useToken, useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
import { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Company} from "~/api/system/company/model";
import type {Tenant} from "~/api/system/tenant/model";
//
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const visible = ref(false);
const showIcon = ref(false);
const activeIndex = ref('mp-weixin');
const list = ref<Tenant[]>();
const avatar = ref(localStorage.getItem('avatar'))
import {
Document,
Menu as IconMenu,
Location,
Expand,
Memo,
Close,
Star,
Setting,
} from '@element-plus/icons-vue'
import {navigateTo} from "#imports";
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
//
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true,
isAdmin: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
function handleCommand(command: string) {
switch (command) {
case 'logOut':
logOut();
break;
default:
navigateTo('/user');
break;
}
}
function logOut() {
token.value = ''
navigateTo('/passport/login')
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const handleSearch = () => {
console.log('搜索')
}
const getUserInfo = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Tenant>>>('/system/tenant/page',{baseURL: runtimeConfig.public.apiServerDev, params: {
userId: localStorage.getItem('UserId')
}})
if(response.value?.data){
console.log(response.value,'tenantList')
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
watch(
() => route.path,
() => {
reload();
getUserInfo();
},
{ immediate: true }
);
</script>
<style lang="less">
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
</style>

183
pages/passport/login.vue

@ -3,19 +3,24 @@
<div class="flash"> <div class="flash">
</div> </div>
<el-card class="m-5 w-screen-sm sm:w-[430px] sm:h-[520px] flex justify-around">
<el-space class="tabs pt-5 text-xl flex justify-center">
<el-tabs v-model="activeName" class="demo-tabs">
<el-card class="m-5 w-screen-sm sm:w-[430px] sm:h-[520px] flex justify-around relative border-0" style="border: 0;">
<div class="login-bar absolute top-0 right-0 cursor-pointer" @click="onLoginBar">
<div class="go-to-register cursor-pointer">
<img src="https://img.alicdn.com/imgextra/i3/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg" alt=""/>
</div>
<span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ loginBar ? '注册' : '登录' }}</span>
</div>
<!-- 登录界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="loginBar">
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="账号登录" name="account"> <el-tab-pane label="账号登录" name="account">
<div class="custom-style my-4"> <div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]"> <el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item> <el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入账号|手机号码" v-model="form.username">
<template #prepend>+86</template>
</el-input>
<el-input class="w-full" size="large" maxlength="30" placeholder="登录账号" :prefix-icon="Avatar" v-model="form.username" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input type="password" size="large" placeholder="登录密码" v-model="form.password" />
<el-input type="password" size="large" maxlength="30" placeholder="登录密码" :prefix-icon="Briefcase" v-model="form.password" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-space class="flex justify-between w-full"> <el-space class="flex justify-between w-full">
@ -43,8 +48,8 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-space class="flex justify-between w-full"> <el-space class="flex justify-between w-full">
<el-input size="large" placeholder="短信验证码" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="sendCode">
<el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">
<span v-if="!countdownTime">发送验证码</span> <span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span> <span v-else>已发送 {{ countdownTime }} s</span>
</el-button> </el-button>
@ -59,7 +64,7 @@
</el-tabs> </el-tabs>
</el-space> </el-space>
<!-- 快捷登录 ---> <!-- 快捷登录 --->
<template v-if="activeName == 'account'">
<template v-if="loginBar && activeName == 'account'">
<div class="clearfix flex justify-center"> <div class="clearfix flex justify-center">
<el-divider> <el-divider>
<span class="text-gray-400">其他登录方式</span> <span class="text-gray-400">其他登录方式</span>
@ -69,6 +74,78 @@
<el-button circle :icon="ElIconUserFilled"></el-button> <el-button circle :icon="ElIconUserFilled"></el-button>
</div> </div>
</template> </template>
<!-- 注册界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="!loginBar">
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="手机号注册" name="sms">
<span class="text-sm text-gray-400">
未注册手机号验证通过后将自动注册
</span>
<div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.phone">
<template #prepend>+86</template>
</el-input>
</el-form-item>
<el-form-item>
<el-space class="flex justify-between w-full">
<el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a>
<a href="#" class="text-gray-700">隐私政策</a>
<a href="#" class="text-gray-700">产品服务协议</a>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<el-tab-pane label="账号密码注册" name="account">
<div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item>
<el-input class="w-full" size="large" maxlength="30" placeholder="登录账号" v-model="form.username" />
</el-form-item>
<el-form-item>
<el-input type="password" size="large" maxlength="30" placeholder="登录密码" v-model="form.password" />
</el-form-item>
<el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.phone">
<template #prepend>+86</template>
</el-input>
</el-form-item>
<el-form-item>
<el-space class="flex justify-between w-full">
<el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a>
<a href="#" class="text-gray-700">隐私政策</a>
<a href="#" class="text-gray-700">产品服务协议</a>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</el-space>
</el-card> </el-card>
</div> </div>
</template> </template>
@ -77,6 +154,7 @@ import {useConfigInfo, useToken, useUser, useWebsite} from "~/composables/config
import useFormData from '@/utils/use-form-data'; import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model'; import type { User } from '@/api/system/user/model';
import { ref } from 'vue' import { ref } from 'vue'
import { Key, Avatar, Briefcase } from '@element-plus/icons-vue'
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {CaptchaResult, LoginResult} from "~/api/passport/login/model"; import type {CaptchaResult, LoginResult} from "~/api/passport/login/model";
@ -90,6 +168,8 @@ const user = useUser();
const activeName = ref('account') const activeName = ref('account')
//
const loginBar = ref<boolean>(true)
// base64 // base64
const captcha = ref(''); const captcha = ref('');
// , // ,
@ -110,6 +190,7 @@ const { form } = useFormData<User>({
password: '', password: '',
code: '', code: '',
smsCode: '', smsCode: '',
isAgree: false,
remember: true, remember: true,
isAdmin: true isAdmin: true
}); });
@ -123,6 +204,18 @@ const openImgCodeModal = () => {
// imgCode.value = text.value; // imgCode.value = text.value;
}; };
const checkUser = async () => {
const {data: hasUser } = await useServerRequest<ApiResult<CaptchaResult>>('/existence',{baseURL: runtimeConfig.public.apiServerDev,method: "get",params: {
field: 'phone', value: form.phone
}});
if(hasUser.value?.code == 0){
await sendCode();
}
if(hasUser.value?.code != 0){
ElMessage.error('该手机号码未注册');
}
};
/* 发送短信验证码 */ /* 发送短信验证码 */
const sendCode = async () => { const sendCode = async () => {
@ -136,7 +229,7 @@ const sendCode = async () => {
const {data: smsCode } = await useServerRequest<ApiResult<CaptchaResult>>('/sendSmsCaptcha',{baseURL: runtimeConfig.public.apiServer,method: "post",body: { const {data: smsCode } = await useServerRequest<ApiResult<CaptchaResult>>('/sendSmsCaptcha',{baseURL: runtimeConfig.public.apiServer,method: "post",body: {
phone: form.phone phone: form.phone
}}); }});
if(smsCode.value){
if(smsCode.value?.code == 0){
codeLoading.value = false; codeLoading.value = false;
countdownTime.value = 30; countdownTime.value = 30;
// //
@ -148,15 +241,23 @@ const sendCode = async () => {
countdownTime.value--; countdownTime.value--;
}, 1000); }, 1000);
} }
if(smsCode.value?.code != 0){
ElMessage.error(smsCode.value?.message);
}
}; };
const navigateTo = (url: string) => { const navigateTo = (url: string) => {
window.location.href = url; window.location.href = url;
} }
const onLoginBar = () => {
loginBar.value = !loginBar.value
activeName.value = loginBar.value ? 'account' : 'sms'
}
/* 获取图形验证码 */ /* 获取图形验证码 */
const changeCaptcha = async () => { const changeCaptcha = async () => {
const {data: captchaInfo } = await useServerRequest<ApiResult<CaptchaResult>>('/captcha',{baseURL: runtimeConfig.public.apiServer});
const {data: captchaInfo } = await useServerRequest<ApiResult<CaptchaResult>>('/captcha',{baseURL: runtimeConfig.public.apiServerDev});
const captchaData = captchaInfo.value?.data const captchaData = captchaInfo.value?.data
if(captchaData){ if(captchaData){
captcha.value = captchaData.base64; captcha.value = captchaData.base64;
@ -180,7 +281,7 @@ useHead({
* 执行登录 * 执行登录
*/ */
const onSubmit = async () => { const onSubmit = async () => {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: runtimeConfig.public.apiServer,method: "post",body: form})
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: runtimeConfig.public.apiServerDev,method: "post",body: form})
// //
if(response.value?.code == 0){ if(response.value?.code == 0){
ElMessage.success(response.value?.message) ElMessage.success(response.value?.message)
@ -196,10 +297,10 @@ const onSubmit = async () => {
* 短信验证码登录 * 短信验证码登录
*/ */
const onSubmitBySms = async () => { const onSubmitBySms = async () => {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/loginBySms',{baseURL: runtimeConfig.public.apiServer,method: "post",body: {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/loginBySms',{baseURL: runtimeConfig.public.apiServerDev,method: "post",body: {
phone: form.phone, phone: form.phone,
code: form.code, code: form.code,
tenantId: 5
isAdmin: true
}}) }})
// //
if(response.value?.code == 0){ if(response.value?.code == 0){
@ -212,6 +313,33 @@ const onSubmitBySms = async () => {
} }
} }
/**
* 账号密码注册
*/
const onRegister = async () => {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/register',{baseURL: runtimeConfig.public.apiServerDev,method: "post",body: {
companyName: '应用名称',
username: form.phone,
phone: form.phone,
password: form.password,
code: form.code,
email: form.email,
isAdmin: true
}})
//
if(response.value?.code == 0){
console.log(response.value)
ElMessage.success(response.value?.message)
await doLogin(response.value.data)
}
if(response.value?.code != 0){
ElMessage.error(response.value?.message)
await changeCaptcha()
}
}
// //
const doLogin = async (data: any) => { const doLogin = async (data: any) => {
const access_token = data?.access_token const access_token = data?.access_token
@ -225,6 +353,8 @@ const doLogin = async (data: any) => {
user.value.gradeName = data.user.gradeName; user.value.gradeName = data.user.gradeName;
user.value.avatar = data.user.avatar; user.value.avatar = data.user.avatar;
localStorage.setItem('UserId',data.user.userId); localStorage.setItem('UserId',data.user.userId);
localStorage.setItem('TID_ADMIN',data.user.tenantId);
// localStorage.setItem('TID_DOMAIN',data.user.tenantId);
} }
setTimeout(() => { setTimeout(() => {
navigateTo('/user') navigateTo('/user')
@ -239,4 +369,27 @@ changeCaptcha();
background: url("https://oss.wsdns.cn/20240904/6f5dc87c37334c4da3453826352a37d1.jpg"); background: url("https://oss.wsdns.cn/20240904/6f5dc87c37334c4da3453826352a37d1.jpg");
background-size: 100%; background-size: 100%;
} }
/* 改变未激活标签的颜色 */
.el-tabs__item {
color: #606266;
font-size: 18px;
}
/* 改变激活标签的颜色 */
//.el-tabs__item.is-active {
// color: #cf1313;
//}
//
///* */
//.el-tabs__item:not(.is-disabled):focus,
//.el-tabs__item:not(.is-disabled):hover {
// border-color: #cf1313;
//}
//
///* */
//.el-tabs--top .el-tabs__active-bar,
//.el-tabs--bottom .el-tabs__active-bar {
// background: #cf1313;
//}
</style> </style>

208
pages/search/index.vue

@ -0,0 +1,208 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<div class="container flex flex-col w-full md:w-screen-xl m-auto my-3">
<Breadcrumb :data="form" :title="`搜索结果`" />
<el-main class="overflow-y-hidden">
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow rounded-lg">
<template #header>我的应用</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-0.5 bg-white mb-4 p-3 flex flex-row items-center hover:bg-gray-50 cursor-pointer">
<el-avatar :src="item?.logo" shape="square" />
<div class="info ml-2">
<div class="app-item-title font-bold flex items-center">{{ item.tenantName }}<el-tag type="warning" class="ml-2" size="small">网页</el-tag></div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
<!-- <el-card shadow="hover" class="mt-4">-->
<!-- <el-tabs v-model="activeIndex" type="">-->
<!-- <el-tab-pane label="微信小程序" name="mp-weixin">-->
<!-- <el-table :data="tableData" stripe style="width: 100%">-->
<!-- <el-table-column prop="date" label="Date" width="180" />-->
<!-- <el-table-column prop="name" label="Name" width="180" />-->
<!-- <el-table-column prop="address" label="Address" />-->
<!-- </el-table>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="网页/移动应用" name="web">-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </el-card>-->
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow ">
<template #header>开发工具推荐</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-5 hover:border-1.5 cursor-pointer">
<div class="info ml-2">
<div class="app-item-title">{{ item.tenantName }}</div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
</el-main>
<!-- 左右结构 -->
<div class="news-box sm:mt-0 mt-2 flex sm:flex-row flex-col justify-between sm:space-x-4">
<div class="left sm:w-18/24">
<!-- 文章列表 -->
<div class="news-list">
<ul class="infinite-list px-3" v-infinite-scroll="load" :infinite-scroll-disabled="disabled">
<li v-for="item in list">
<div class="item flex justify-between py-3 px-4 gap-xl mb-5 rounded-lg mb-3 bg-white hover:shadow">
<div class="item-info py-2 flex flex-col justify-between">
<div class="title line-clamp-2 overflow-hidden text-ellipsis group-hover:group-hover:text-ellipsis">
<a :href="`/article/detail/${item.articleId}`" target="_blank" class="text-xl">{{ item.title }}</a>
</div>
<div class="desc max-w-22/24 text-gray-5 sm:block hidden" v-html="item.comments"></div>
<div class="actions text-gray-4 text-sm flex gap-2xl">
<span href="#">{{ item.updateTime }}</span>
<span href="#">浏览{{ item.actualViews }}</span>
</div>
</div>
<div class="item-image flex items-center" @click="navigateTo(`/article/detail/${item.articleId}`)">
<el-image :src="item.image" lazy class="sm:w-[140px] sm:h-[140px] w-[110px] h-[110px] bg-gray-50 cursor-pointer transition rounded-lg sm:rounded-lg ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300" fit="contain" />
</div>
</div>
</li>
</ul>
<div class="text-center text-gray-4">
<text v-if="disabled">没有更多了</text>
<text @click="load" v-else>加载更多</text>
</div>
</div>
</div>
<div class="right sm:mt-0 mt-4 sm:w-6/24">
<!-- 推荐文章 -->
<div class="category-item bg-white rounded-lg p-3 hover:shadow">
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2">
<el-icon>
<ElIconLink/>
</el-icon>
<text class="ml-2">推荐文章</text>
</div>
<div class="flex flex-wrap px-2 py-4">
<div v-for="(item,index) in links" class="flex items-center">
<a :href="item.url" target="_blank">{{ item.name }}</a>
<el-divider v-if="index + 1 != links.length" direction="vertical" />
</div>
</div>
</div>
<div class="category-item mt-4 bg-white rounded-lg p-3 hover:shadow">
<div class="category-name text-lg text-gray-600 border-b border-gray-200 border-b-solid pb-2 mx-2">
<el-icon>
<ElIconLink/>
</el-icon>
<text class="ml-2">点击排行</text>
</div>
<div class="flex flex-wrap px-2 py-4">
<div v-for="(item,index) in links" class="flex items-center">
<a :href="item.url" target="_blank">{{ item.name }}</a>
<el-divider v-if="index + 1 != links.length" direction="vertical" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <el-divider />-->
<!-- <div v-if="!list">-->
<!-- <el-empty description="404 页面不存在"></el-empty>-->
<!-- </div>-->
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import type {Article} from "~/api/cms/article/model";
import {useServerRequest} from "~/composables/useServerRequest";
import Breadcrumb from "~/components/Breadcrumb.vue";
import {useForm, useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdByParam, getIdBySpm, getPath} from "~/utils/common";
const route = useRoute();
//
const list = ref<Article[]>([]);
const title = ref();
const categoryName = ref();
const count = ref()
const links = ref<any[]>();
const page = ref<number>(0);
const disabled = ref<boolean>(false);
const newList = ref<Article[]>();
const layout = ref<any>();
//
const form = ref<Navigation>();
const website = useWebsite();
const load = () => {
if(!disabled.value){
page.value++;
// reload();
}
}
//
const reload = async () => {
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/navigation/getNavigationByPath',{query: {path: getPath()}})
if(nav.value?.data){
form.value = nav.value?.data;
}
//
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
console.log(layout.value)
//
const { data: articleList } = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/article/page',{
params: {
page: page.value,
categoryId: getIdByParam()
}
})
if(articleList.value?.data){
list.value = articleList.value.data.list
}
console.log(list.value)
useHead({
title: `${form.value.title} - ${website.value.websiteName}`,
meta: [{ name: form.value.design?.keywords, content: form.value.design?.description }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: "console.log('Hello World')",
},
],
});
}
watch(
() => route,
() => {
reload();
},
{ immediate: true }
);
</script>

2
utils/common.ts

@ -123,7 +123,7 @@ export function openSpmUrl(path: string, d?: any, id = 0): void {
// TODO 含http直接跳转 // TODO 含http直接跳转
if (path.slice(0, 4) == 'http') { if (path.slice(0, 4) == 'http') {
window.location.href = `${path}${spm.value}`;
window.open(`${path}${spm.value}`);
return; return;
} }

Loading…
Cancel
Save