Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
fancl | 75ca055772 | |
fancl | 7d1ecab9fb | |
fancl | e7fc3614a8 | |
fancl | 84c9206d22 |
|
@ -0,0 +1,11 @@
|
||||||
|
GOPATH:=$(shell go env GOPATH)
|
||||||
|
DATETIME:=$(shell date "+%Y-%m-%d %H:%M:%S")
|
||||||
|
PKGNAME:="git.nobla.cn/golang/moto"
|
||||||
|
GIT_VERSION=$(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "-s -w -X '$(PKGNAME)/version.Version=$(GIT_VERSION)' -X '$(PKGNAME)/version.BuildDate=$(DATETIME)'" -o ./bin/$(APP_NAME) ./cmd/main.go
|
|
@ -10,4 +10,4 @@ adminUsers:
|
||||||
system:
|
system:
|
||||||
settings:
|
settings:
|
||||||
productName: "测试网址"
|
productName: "测试网址"
|
||||||
copyright: "xxx"
|
copyright: "版权所有,防盗必究"
|
|
@ -0,0 +1,192 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.nobla.cn/golang/kos/util/env"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
packageNameFlag = flag.String("package-name", "", "应用程序包名称")
|
||||||
|
versionFlag = flag.String("version", env.Get("MOTO_VERSION", "v0.0.3"), "模板版本号,可以使用环境变量 MOTO_VERSION")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
oldPackageName = "git.nobla.cn/golang/moto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeFile(dstFile string, r io.Reader) (err error) {
|
||||||
|
var (
|
||||||
|
fp *os.File
|
||||||
|
)
|
||||||
|
if fp, err = os.Create(dstFile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
_, err = io.Copy(fp, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFile(file string, dirname string) (err error) {
|
||||||
|
var (
|
||||||
|
prefix string
|
||||||
|
fp *os.File
|
||||||
|
gzipReader *gzip.Reader
|
||||||
|
)
|
||||||
|
// 清理路径字符串
|
||||||
|
dirname = path.Clean(dirname)
|
||||||
|
|
||||||
|
// 打开压缩文件
|
||||||
|
if fp, err = os.Open(file); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
// 执行解压操作
|
||||||
|
if gzipReader, err = gzip.NewReader(fp); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gzipReader.Close()
|
||||||
|
tarReader := tar.NewReader(gzipReader)
|
||||||
|
for {
|
||||||
|
header, errRead := tarReader.Next()
|
||||||
|
if errRead != nil {
|
||||||
|
if errors.Is(errRead, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = errRead
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if header.Typeflag == tar.TypeXGlobalHeader {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if prefix == "" && header.Typeflag == tar.TypeDir {
|
||||||
|
prefix = header.Name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
realname := strings.TrimPrefix(header.Name, prefix)
|
||||||
|
if strings.HasPrefix(realname, "generator") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filename := path.Join(dirname, realname)
|
||||||
|
if header.Typeflag == tar.TypeDir {
|
||||||
|
if err = os.MkdirAll(filename, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = writeFile(filename, tarReader); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
os.Chmod(filename, 0644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadPackage(version string, dirname string) (err error) {
|
||||||
|
var (
|
||||||
|
uri string
|
||||||
|
res *http.Response
|
||||||
|
)
|
||||||
|
uri = "https://git.nobla.cn/golang/moto/archive/" + version + ".tar.gz"
|
||||||
|
if res, err = http.Get(uri); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
res.Body.Close()
|
||||||
|
}()
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
err = errors.New(res.Status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filename := path.Join(os.TempDir(), strconv.FormatInt(time.Now().UnixMilli(), 10)+".tar.gz")
|
||||||
|
if err = writeFile(filename, res.Body); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = extractFile(filename, dirname)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceFiles(dirname string, source, replace string) (err error) {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
files []os.DirEntry
|
||||||
|
)
|
||||||
|
if files, err = os.ReadDir(dirname); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb := []byte(source)
|
||||||
|
rb := []byte(replace)
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name() == "." || file.Name() == ".." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filename := path.Join(dirname, file.Name())
|
||||||
|
if file.IsDir() {
|
||||||
|
err = replaceFiles(filename, source, replace)
|
||||||
|
} else {
|
||||||
|
if buf, err = os.ReadFile(filename); err == nil {
|
||||||
|
buf = bytes.ReplaceAll(buf, sb, rb)
|
||||||
|
os.WriteFile(filename, buf, 064)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
pos int
|
||||||
|
err error
|
||||||
|
dirname string
|
||||||
|
appname string
|
||||||
|
packageName string
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
packageName = *packageNameFlag
|
||||||
|
if packageName == "" {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
packageName = os.Args[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = strings.LastIndexByte(packageName, '/')
|
||||||
|
if pos == -1 {
|
||||||
|
fmt.Println("应用包名称是无效的")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
appname = packageName[pos:]
|
||||||
|
if dirname, err = os.Getwd(); err != nil {
|
||||||
|
fmt.Println("获取应用目录失败:" + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
dirname = path.Join(dirname, appname)
|
||||||
|
if err = os.MkdirAll(dirname, 0755); err != nil {
|
||||||
|
fmt.Println("创建应用目录失败:" + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err = downloadPackage(*versionFlag, dirname); err != nil {
|
||||||
|
fmt.Println("下载模板文件失败:" + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err = replaceFiles(dirname, oldPackageName, packageName); err != nil {
|
||||||
|
fmt.Println("生产应用文件失败:" + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("创建项目成功")
|
||||||
|
}
|
|
@ -60,6 +60,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
--el-border-color: var(--form-control-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.el-input {
|
.el-input {
|
||||||
--el-input-border-color: var(--form-control-border-color);
|
--el-input-border-color: var(--form-control-border-color);
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,9 @@
|
||||||
<div class="header-avatar">
|
<div class="header-avatar">
|
||||||
<el-avatar :size="36" :title="username" :src="avatar">
|
<el-avatar :size="36" :title="username" :src="avatar">
|
||||||
</el-avatar>
|
</el-avatar>
|
||||||
<i class="user-status" :style="{ backgroundColor: userStateColor }" :title="userStateText"
|
|
||||||
@click="dialogVisible = true"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item command="status">设置状态</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="profile">个人设置</el-dropdown-item>
|
<el-dropdown-item command="profile">个人设置</el-dropdown-item>
|
||||||
<el-dropdown-item divided command="logout">退出系统</el-dropdown-item>
|
<el-dropdown-item divided command="logout">退出系统</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
|
@ -43,28 +40,10 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog class="status-dialog" v-model="dialogVisible" width="320px" draggable>
|
|
||||||
<div class="current-status">
|
|
||||||
<div>
|
|
||||||
<i :style="{ backgroundColor: userStateColor }"></i> <span>{{ userStateText }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="8" v-for="item in userStatus">
|
|
||||||
<div class="status-item" @click="handleSetStatus(item)"
|
|
||||||
:class="item.value === userState ? 'active' : ''">
|
|
||||||
<div class="status-avatar">
|
|
||||||
<i :style="{ backgroundColor: item.color }"></i>
|
|
||||||
</div>
|
|
||||||
<div class="status-text">{{ item.label }}</div>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, inject, onMounted, onUnmounted, ref } from 'vue';
|
import { inject, onMounted, onUnmounted } from 'vue';
|
||||||
import useSystemStore from '@/stores/system'
|
import useSystemStore from '@/stores/system'
|
||||||
import useThemeStore from '@/stores/theme'
|
import useThemeStore from '@/stores/theme'
|
||||||
import useUserStore from '@/stores/user'
|
import useUserStore from '@/stores/user'
|
||||||
|
@ -72,15 +51,11 @@ import { storeToRefs } from 'pinia';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
import Icon from '@/components/widgets/Icon.vue';
|
import Icon from '@/components/widgets/Icon.vue';
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { getUserStatus, getStatusText, getStatusTextColor } from '@/assets/js/status'
|
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
|
|
||||||
|
|
||||||
const systemStore = useSystemStore();
|
const systemStore = useSystemStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const userState = ref('idle');
|
|
||||||
const dialogVisible = ref(false);
|
|
||||||
|
|
||||||
const { logoUrl, productName } = storeToRefs(systemStore);
|
const { logoUrl, productName } = storeToRefs(systemStore);
|
||||||
const { headerBackgroundColor } = storeToRefs(themeStore);
|
const { headerBackgroundColor } = storeToRefs(themeStore);
|
||||||
|
@ -91,17 +66,6 @@ const logout = inject('logout');
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const userStatus = computed(() => {
|
|
||||||
return getUserStatus();
|
|
||||||
})
|
|
||||||
|
|
||||||
const userStateText = computed(() => {
|
|
||||||
return getStatusText(userState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const userStateColor = computed(() => {
|
|
||||||
return getStatusTextColor(userState.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleFullscreen = (e) => {
|
const handleFullscreen = (e) => {
|
||||||
if (screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
|
@ -117,15 +81,8 @@ const handleToggleMenuVisible = (e) => {
|
||||||
systemStore.toggleFlowSidebarVisible()
|
systemStore.toggleFlowSidebarVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSetStatus = (e) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMenuCommand = (e) => {
|
const handleMenuCommand = (e) => {
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case 'status':
|
|
||||||
dialogVisible.value = true;
|
|
||||||
break;
|
|
||||||
case 'profile':
|
case 'profile':
|
||||||
router.push('/organize/user/profile')
|
router.push('/organize/user/profile')
|
||||||
break
|
break
|
||||||
|
|
|
@ -40,8 +40,6 @@ import { storeToRefs } from 'pinia';
|
||||||
import useThemeStore from '@/stores/theme'
|
import useThemeStore from '@/stores/theme'
|
||||||
import useSystemStore from '@/stores/system'
|
import useSystemStore from '@/stores/system'
|
||||||
import useUserStore from '@/stores/user'
|
import useUserStore from '@/stores/user'
|
||||||
import { getBaseHost } from '@/apis/request'
|
|
||||||
import { updateStatusMap } from '@/assets/js/status'
|
|
||||||
import { userLogout, getUserProfile } from '@/apis/organize'
|
import { userLogout, getUserProfile } from '@/apis/organize'
|
||||||
import { getConfigure } from '@/apis/system'
|
import { getConfigure } from '@/apis/system'
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,19 @@
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<div class="flex-shrink">
|
<div class="flex-shrink">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-fill"></div>
|
<div class="flex-fill">
|
||||||
|
<div class="text-right text-muted">{{ copyright }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import useSystemStore from '@/stores/system'
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
|
||||||
|
const systemStore = useSystemStore()
|
||||||
|
|
||||||
|
const { copyright } = storeToRefs(systemStore);
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -10,7 +10,7 @@ const useSystemStore = defineStore('system', {
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
logoUrl: '//s3.tebi.io/tenos/images/logo/jc.png',
|
logoUrl: '//s3.tebi.io/tenos/images/logo/jc.png',
|
||||||
copyright: '2005-2023 JUSTCALL 版权 © 2023 集时股份呼叫中心开发团队',
|
copyright: '2005-2023 JUSTCALL 版权 © 2023 集时股份呼叫中心开发团队',
|
||||||
productName: '在线系统',
|
productName: '管理系统',
|
||||||
variables: {},
|
variables: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue