This commit is contained in:
oneao committed 2026-04-02 17:24:43 +08:00
1 parent 9e00adf2fe
commit ae0d245605
34 files changed
+1460 -162

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>
+13
View File
@@ -0,0 +1,13 @@
FROM golang:1.25.3 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o app ./cmd/main.go
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
+7 -6
View File
@@ -3,17 +3,18 @@ package main
import (
"allapp-go-v2/internal/bootstrap"
"allapp-go-v2/internal/server"
"log"
)
func main() {
app, err := bootstrap.NewApp()
app, err := bootstrap.Start()
if err != nil {
log.Fatal(err)
panic(err)
}
if err := server.Start(app); err != nil {
log.Fatal(err)
}
err = server.Run(app.Config)
if err != nil {
panic(err)
}
}
@@ -0,0 +1,2 @@
server:
port: 8086
@@ -0,0 +1,17 @@
version: "3.8"
services:
app:
build: .
container_name: allapp
ports:
- "8080:8080"
environment:
- APP_ENV=prod
volumes:
- .:/app
restart: always
@@ -12,7 +12,7 @@ type App struct {
Db *pgxpool.Pool
}
func NewApp() (*App, error) {
func Start() (*App, error) {
cfg, err := config.Load()
if err != nil {
return nil, err
@@ -1,26 +1,21 @@
package server
import (
"allapp-go-v2/internal/bootstrap"
"fmt"
"allapp-go-v2/internal/config"
"log"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/requestid"
)
func Start(appCtx *bootstrap.App) error {
// 1️⃣ 创建 fiber 实例
func Run(cfg *config.Config) error {
app := fiber.New()
app.Use(requestid.New())
// 注册路由
//registerRoutes(app)
// 2️⃣ 注册路由
//registerRoutes(app, appCtx)
port := cfg.Server.Port
// 3️⃣ 启动服务
addr := fmt.Sprintf(":%s", appCtx.Config.Server.Port)
log.Println("🚀 server start on :" + port)
fmt.Println("🚀 server running at", addr)
return app.Listen(addr)
return app.Listen(":" + port)
}
@@ -0,0 +1,3 @@
{"level":"INFO","time":"2026-04-02 15:02:57","caller":"bootstrap/app.go:28","msg":"√ 数据库连接成功"}
{"level":"INFO","time":"2026-04-02 15:03:21","caller":"bootstrap/app.go:28","msg":"√ 数据库连接成功"}
{"level":"INFO","time":"2026-04-02 15:03:31","caller":"bootstrap/app.go:28","msg":"√ 数据库连接成功"}
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoImports">
<option name="excludedPackages">
<array>
<option value="github.com/pkg/errors" />
<option value="golang.org/x/net/context" />
</array>
</option>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/allapp-go-v3.iml" filepath="$PROJECT_DIR$/.idea/allapp-go-v3.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"allapp-go/internal/bootstrap"
)
func main() {
_ = bootstrap.BootService()
}
@@ -0,0 +1,2 @@
app:
port: 8087
Whitespace-only changes.
@@ -0,0 +1,3 @@
app:
name: allapp
port: 8080
+36
View File
@@ -0,0 +1,36 @@
module allapp-go
go 1.25.0
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gofiber/fiber/v3 v3.1.0 // indirect
github.com/gofiber/schema v1.7.0 // indirect
github.com/gofiber/utils/v2 v2.0.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.1 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tinylib/msgp v1.6.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.69.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
)
+72
View File
@@ -0,0 +1,72 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI=
github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -0,0 +1,24 @@
package bootstrap
import (
"allapp-go/internal/config"
"log"
)
type App struct {
Config *config.Config
}
func BootService() *App {
// 1️⃣ 加载配置
cfg, err := config.Load()
if err != nil {
log.Fatalf("❌ load config failed: %v", err)
}
RunServer(cfg)
return &App{
Config: cfg,
}
}
@@ -0,0 +1,24 @@
package bootstrap
import (
"allapp-go/internal/config"
"allapp-go/internal/router"
"fmt"
"log"
"github.com/gofiber/fiber/v3"
)
func RunServer(cfg *config.Config) {
app := fiber.New()
router.Setup(app)
addr := fmt.Sprintf(":%d", cfg.App.Port)
log.Printf("🚀 Server running on %s", addr)
if err := app.Listen(addr); err != nil {
log.Fatalf("❌ start server failed: %v", err)
}
}
@@ -0,0 +1,21 @@
package config
type Config struct {
App AppConfig `mapstructure:"app"`
Postgres PostgresConfig `mapstructure:"postgres"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Port int `mapstructure:"port"`
}
type PostgresConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
SslMode bool `mapstructure:"ssl_mode"`
TimeZone string `mapstructure:"timezone"`
}
@@ -0,0 +1,50 @@
package config
import (
"fmt"
"os"
"strings"
"github.com/spf13/viper"
)
func Load() (*Config, error) {
env := getEnv("APP_ENV", "dev")
v := viper.New()
v.SetConfigType("yaml")
// 基础配置
v.AddConfigPath("configs")
v.SetConfigName("config")
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("read base config failed: %w", err)
}
// 环境配置(覆盖)
envConfigName := fmt.Sprintf("config.%s", env)
v.SetConfigName(envConfigName)
if err := v.MergeInConfig(); err != nil {
// dev 允许没有文件,prod 建议必须存在
if env == "prod" {
return nil, fmt.Errorf("read env config failed: %w", err)
}
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("unmarshal config failed: %w", err)
}
return &cfg, nil
}
func getEnv(key, defaultVal string) string {
val := os.Getenv(key)
if val == "" {
return defaultVal
}
return strings.ToLower(val)
}
@@ -0,0 +1,16 @@
package router
import (
"github.com/gofiber/fiber/v3"
)
func Setup(app *fiber.App) {
// 健康检查
app.Get("/ping", func(c fiber.Ctx) error {
return c.SendString("pong")
})
// 你后面可以这样分组:
// setupUserRoutes(app)
// setupOrderRoutes(app)
}
@@ -1,11 +1,486 @@
<script lang="ts" setup>
<script setup lang="ts">
import { toast } from 'sard-uniapp'
import { ref } from 'vue'
import ShDialog from '@/components/sh-dialog/index.vue'
import { useDeviceInfo } from '@/hooks/useDeviceInfo'
import { loaddataApi, searchdatapageindexApi } from '@/request/api'
import { useUserStore } from '@/store'
import { useCountsStore } from '@/store/counts'
import { groupFieldsBySpan } from '@/utils/common'
const countsStore = useCountsStore()
const userStore = useUserStore()
const { isMobile } = useDeviceInfo()
const auditComponentData = ref({
moduleId: 'car_daily',
saveTable: 'car_daily',
state: '',
row: null,
})
const pagingRef = ref<any>(null)
const dataList = ref<any[]>([])
const modLevelList = ref<any[]>([])
const showShDialog = ref(false)
// 自定义 tabs
const tabList = computed(() => [
{ name: '待审', count: countsStore.baoxiao.v_dsh || 0 },
{ name: '已处理', count: countsStore.baoxiao.v_ash || 0 },
{ name: '全部', count: countsStore.baoxiao.all_cnt || 0 },
])
const activeTab = ref(tabList.value[0])
const itemFields = [
{ label: '账单日期', value: item => item.b_date, span: 12 },
{ label: '结算单位', value: item => item.v_dw_name, span: 12 },
{ label: '报销金额', value: item => item.b_amount_root, span: 6 },
{ label: '币种', value: item => item.b_money_id, span: 6 },
{ label: '汇率', value: item => item.b_exrate, span: 6 },
{ label: '折后金额', value: item => item.b_amount, span: 6 },
]
const groupedFields = groupFieldsBySpan(itemFields)
// 搜索关键字
const searchValue = ref('')
onShow(async () => {
await loadModLevelList()
// await pagingRef.value?.reload()
countsStore.loadTaskCounts(['baoxiao'])
})
async function loadModLevelList() {
await loaddataApi({
viewname: 'v_sh_b_set',
searchcondition: `b_user_id='${userStore.userInfo.userid}' and b_module_id = 'car_daily'`,
orderfield: 'b_level',
soption: '',
}).then((res) => {
modLevelList.value = res.data
})
}
// 切换 tab
function switchTab(tab: any) {
activeTab.value = tab
pagingRef.value.reload()
}
// 搜索
function handleSearch() {
pagingRef.value.reload()
}
function getTagType(state) {
switch (state) {
case '审核通过':
return 'success'
case '草拟':
return 'info'
case '审核驳回':
return 'danger'
default:
return 'primary'
}
}
// 分页请求
async function queryList(pageNo: number, pageSize: number) {
toast.loading('正在加载中')
const userid = userStore.userInfo.userid
let sql = '2 > 1 '
let mlvSearch = ''
let slevel = ''
for (let i in modLevelList.value) {
let item = modLevelList.value[i]
slevel = item.b_level
let viewname = item.b_ViewTable
let condition = item.b_condition
let j = Number.parseInt(slevel) - 1
if (mlvSearch !== '') {
mlvSearch = `${mlvSearch} or `
}
mlvSearch = `${mlvSearch}(b_module_id = '${item.b_module_id}'`
if (viewname !== '' && condition !== '') {
mlvSearch = `${mlvSearch} and b_id in (select b_id from ${viewname} where ${condition})`
}
let canlevel = ` and b_level in('${j}','${slevel}')`
if (activeTab.value.name === '待审') {
canlevel = ` and b_level = '${j}'`
}
if (activeTab.value.name === '已处理') {
canlevel = ` and b_level = '${slevel}'`
}
if (canlevel !== '') {
mlvSearch = mlvSearch + canlevel
}
mlvSearch = `${mlvSearch})`
}
if (mlvSearch) {
sql = `${sql} and (${mlvSearch})`
}
if (searchValue.value) {
sql = `${sql} and b_id like '%${searchValue.value}%'`
}
searchdatapageindexApi({
viewname: 'v_sh_recorddata',
searchcondition: sql,
userid,
page: pageNo,
rows: pageSize,
orderfield: 'b_ss_date desc',
})
.then((res) => {
const { rows, _total } = res.data
let idList = []
rows.forEach((row) => {
idList.push(row.b_id)
})
let sql2 = '2 > 1'
if (idList.length > 0) {
let idString = idList.map(id => `'${id}'`).join(',')
sql2 = `b_id IN (${idString})`
}
else {
sql2 = `b_id IN (NULL)`
}
loaddataApi({
viewname: 'v_car_daily',
searchcondition: sql2,
orderfield: 'b_date desc',
soption: '',
}).then((res) => {
let rows2 = res.data
rows2.forEach((row) => {
if (row.b_id) {
let slevel: string | number = ''
let cansh = false
let canbh = false
let bModuleId = ''
let bLevel: string | number = ''
const foundItem = rows.find(item => item.b_id === row.b_id)
if (foundItem) {
bModuleId = foundItem.b_module_id
bLevel = foundItem.b_level
row.b_module_id = bModuleId
}
for (const modLevel of modLevelList.value) {
if (modLevel.b_module_id === bModuleId) {
slevel = modLevel.b_level
slevel = Number.parseInt(slevel as string)
if (Number.isNaN(slevel)) {
slevel = 0
}
let j = slevel - 1
if (bLevel === j) {
cansh = true
}
if (bLevel === slevel) {
canbh = true
}
}
}
if (canbh || cansh) {
if (cansh) {
row.tongGuoButton = true
row.boHuiButton = true
}
else if (canbh) {
row.tongGuoButton = false
row.boHuiButton = true
}
}
else {
row.tongGuoButton = false
row.boHuiButton = false
}
}
})
if (rows2) {
console.log(rows2)
pagingRef.value.complete(rows2)
}
})
})
.finally(() => {
toast.hide()
})
}
async function handleApproval(item: any, state: string) {
auditComponentData.value.row = item
auditComponentData.value.state = state
showShDialog.value = true
}
function handleShSuccess() {
pagingRef.value.reload()
countsStore.loadTaskCounts(['baoxiao'])
}
function handleToProcess(item) {
uni.navigateTo({
url: `/pages-sub/process/index?b_id=${item.b_id}&b_module_id=${item.b_module_id}&v_modulename=费用报销`,
})
}
</script>
<template>
<view class="" />
<view>
<z-paging ref="pagingRef" v-model="dataList" auto-show-back-to-top @query="queryList">
<template #top>
<sar-search
v-model="searchValue"
placeholder="业务单号"
@search="handleSearch"
/>
<view class="tabs">
<view
v-for="tab in tabList"
:key="tab.name"
class="tabs__item"
:class="{ 'tabs__item--active': activeTab.name === tab.name }"
@click="switchTab(tab)"
>
<sar-badge v-if="tab.count > 0" :value="tab.count">
{{ tab.name }}
</sar-badge>
<text v-else>
{{ tab.name }}
</text>
</view>
</view>
</template>
<!-- 列表内容 -->
<view
v-for="(item, index) in dataList"
:key="index"
class="list-item"
:class="[!isMobile ? 'list-item--desktop' : '']"
@click="handleToProcess(item)"
>
<view class="list-item__header">
<view class="list-item__main">
<!-- <sar-checkbox size="36rpx" /> -->
<text>
{{ item.b_id }}
</text>
</view>
<view class="list-item__extra">
<sar-tag :theme="getTagType(item.b_state)">
{{ item.b_state }}
</sar-tag>
</view>
</view>
<view class="list-item__body">
<sar-row v-for="(row, rowIndex) in groupedFields" :key="rowIndex">
<sar-col
v-for="(col, colIndex) in row"
:key="colIndex"
:span="col.span || 6"
>
<view class="list-item__info">
<view class="list-item__label">
{{ col.label }}:
</view>
<view class="list-item__value">
{{ col.value(item) }}
</view>
</view>
</sar-col>
</sar-row>
</view>
<view class="list-item__footer">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click.stop="handleApproval(item, '审核驳回')">
驳回
</sar-button>
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click.stop="handleApproval(item, '审核通过')">
通过
</sar-button>
</view>
<view class="list-item__line" />
</view>
<!-- <template #bottom>
<view class="bottom-bar">
<sar-checkbox>
全选
</sar-checkbox>
<sar-button block size="small" icon="success">
批量通过
</sar-button>
</view>
</template> -->
</z-paging>
<ShDialog v-model="showShDialog" :component-data="auditComponentData" @success="handleShSuccess" />
</view>
</template>
<style lang="scss" scoped>
<style scoped lang="scss">
@import '@/style/mixins.scss';
.dialog-loading {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
}
.bottom-bar {
display: flex;
align-items: center;
padding: 24rpx 36rpx;
background: #fff;
border-top: 2rpx solid #ddd;
@include flexGap(32rpx, '');
}
/* tab 栏 */
.tabs {
display: flex;
border-bottom: 2rpx solid #eee;
background-color: #fff;
.tabs__item {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 30rpx;
color: #888;
}
.tabs__item--active {
color: var(--sar-primary);
font-weight: bold;
border-bottom: 4rpx solid var(--sar-primary);
}
}
/* 列表项 */
.list-item {
position: relative;
padding: 24rpx 16rpx;
background-color: #fff;
transition: all 0.2s;
&:active {
background-color: #f5f5f5;
}
&.list-item--desktop {
.list-item__header {
max-width: 1000rpx;
margin: 0 auto;
}
.list-item__body {
max-width: 1000rpx;
margin: 0 auto;
}
.list-item__footer {
max-width: 1000rpx;
margin: 0 auto;
}
}
.list-item__header {
display: flex;
justify-content: space-between;
.list-item__main {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
font-weight: bold;
font-size: 36rpx;
}
.list-item__extra {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
.list-item__finishdate {
font-size: 28rpx;
color: #666;
}
}
}
.list-item__body {
padding: 24rpx 0rpx;
display: flex;
flex-direction: column;
@include flexGap(16rpx, 'column');
.list-item__info {
display: flex;
align-items: start;
width: 100%;
font-size: 32rpx;
line-height: 1;
.list-item__label {
display: block;
min-width: 192rpx;
color: #4b5563;
text-align: right;
white-space: nowrap;
}
.list-item__value {
flex: 1;
color: #111827;
word-break: break-all;
}
}
}
.list-item__footer {
display: flex;
justify-content: flex-end;
@include flexGap(36rpx, '');
:deep(.sar-button) {
padding: 0rpx 48rpx;
font-size: 32rpx;
}
}
.list-item__line {
position: absolute;
bottom: 0rpx;
left: 0rpx;
height: 1px;
width: 100%;
background-color: #dcdcdc;
}
}
</style>
@@ -44,8 +44,6 @@ async function loadFileData() {
url: item.mx_mapfilename,
}
})
console.log(fileList.value)
})
}
@@ -59,13 +59,11 @@ onShow(async () => {
async function loadModLevelList() {
await loaddataApi({
viewname: 'v_sh_b_set',
searchcondition: `b_user_id='${userStore.userInfo.userid}'`,
searchcondition: `b_user_id='${userStore.userInfo.userid}' AND b_module_id = 'cw_zdorder_f'`,
orderfield: 'b_level',
soption: '',
}).then((res) => {
modLevelList.value = res.data.filter(item => item.b_module_id === 'cw_zdorder_f')
console.log(modLevelList.value)
modLevelList.value = res.data
})
}
@@ -149,12 +147,13 @@ async function queryList(pageNo: number, pageSize: number) {
orderfield: 'b_ss_date desc',
})
.then((res) => {
const { rows, _total } = res.data
const { rows, total } = res.data
let idList = []
rows.forEach((row) => {
idList.push(row.b_id)
})
let sql2 = '2 > 1'
if (idList.length > 0) {
let idString = idList.map(id => `'${id}'`).join(',')
@@ -319,10 +318,10 @@ function handleToProcess(item) {
</view>
<view class="list-item__footer">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click="handleApproval(item, '审核驳回')">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click.stop="handleApproval(item, '审核驳回')">
驳回
</sar-button>
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click="handleApproval(item, '审核通过')">
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click.stop="handleApproval(item, '审核通过')">
通过
</sar-button>
</view>
@@ -421,9 +420,9 @@ function handleToProcess(item) {
.list-item__main {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
font-weight: bold;
font-size: 36rpx;
@include flexGap(16rpx, '');
}
.list-item__extra {
display: flex;
@@ -0,0 +1,59 @@
<script setup lang="ts">
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { loaddataAttachApi } from '@/request/api'
const props = defineProps({
componentData: {
type: Object,
},
})
const fileList = ref<UploadFileItem[]>([])
const father = ref('')
const moduleid = ref('')
const cateid = ref('')
onLoad(() => {
father.value = props.componentData.b_id
moduleid.value = props.componentData.b_module_id
nextTick(() => {
loadFileData()
})
})
async function loadFileData() {
let searchcondition = ` father = '${father.value}' and mx_moduleid = '${moduleid.value}'`
if (cateid.value !== '') {
searchcondition += ` and mx_cate_id = '${cateid.value}'`
}
loaddataAttachApi({
viewname: 'bf_files',
searchcondition,
orderfield: 'mx_xh',
soption: '',
}).then((res) => {
fileList.value = res.data.map((item) => {
return {
...item,
name: item.mx_filename,
url: item.mx_mapfilename,
}
})
})
}
</script>
<template>
<view class="file">
<sar-result v-if="fileList.length === 0" status="info" title="提示" description="暂无附件" />
<sar-upload v-model="fileList" :readonly="true" />
</view>
</template>
<style lang="scss" scoped>
.file {
padding: 36rpx;
}
</style>
@@ -1,4 +1,5 @@
<script lang="ts" setup>
import File from './file.vue'
import Information from './information.vue'
import Step from './step.vue'
@@ -69,6 +70,7 @@ onLoad((options) => {
<Step v-if="segmentedValue === '流程'" :component-data="data" />
<Information v-else-if="segmentedValue === '详细'" :component-data="data" />
<File v-else-if="segmentedValue === '附件'" :component-data="data" />
</view>
</template>
@@ -1,5 +1,4 @@
<script lang="ts" setup>
import LoadingPage from '@/components/loading-page/index.vue'
import { loaddataApi } from '@/request/api'
const props = defineProps({
@@ -7,33 +6,124 @@ const props = defineProps({
type: Object,
},
})
const data = ref<any[]>([])
const loading = ref<boolean>(true)
const loading = ref(true)
const stepsData = ref<Array<any>>([])
const progress = ref({
finish: 0,
all: 0,
})
const computedProgress = computed(() => {
if (progress.value.all === 0) {
return 0
}
return Math.round((progress.value.finish / progress.value.all) * 100)
})
onLoad(() => {
initData()
})
async function initData() {
loading.value = true
await loaddataApi({
viewname: 'v_sh_record',
orderfield: 'b_id desc',
// 定义两个请求
const request1 = loaddataApi({
viewname: 'v_sh_setjd',
orderfield: 'b_level',
searchcondition: `b_module_id = '${props.componentData.b_module_id}'`,
soption: '',
})
const request2 = loaddataApi({
viewname: 'v_sh_modshrecord',
orderfield: 'b_level',
searchcondition: `b_module_id = '${props.componentData.b_module_id}' and b_dj_id = '${props.componentData.b_id}'`,
soption: '',
}).then((res) => {
data.value = res.data
})
loading.value = false
// 使用 Promise.all 等待两个请求都完成
Promise.all([request1, request2])
.then(([res1, res2]) => {
const stepData = res1.data
const stepItemData = res2.data
progress.value.finish = stepItemData.length
progress.value.all = stepData.length
let current = true
stepData.forEach((item, index) => {
const obj = {
type: 0,
current: false,
b_name: '',
v_user_name: '',
b_sh_memo: '',
b_sh_datetime: '',
b_level: item.b_level,
consume_time: '',
}
const stepItem = stepItemData[index]
if (stepItem !== undefined) {
obj.type = 2
obj.v_user_name = stepItem.v_user_name
obj.b_sh_memo = stepItem.b_sh_memo
obj.b_sh_datetime = stepItem.b_sh_datetime
obj.b_name = stepItem.b_name
// 计算审批耗时,需要计算与上一级的耗时
if (obj.b_level !== 0) {
// 计算与上一个的耗时
const lastItem = stepItemData[index - 1]
obj.consume_time = computedTimeDifference(lastItem.b_sh_datetime, obj.b_sh_datetime)
}
}
else {
if (current) {
obj.current = true
obj.type = 1
current = false
}
if (item.b_level === 0) {
obj.b_name = `送审`
}
else {
obj.b_name = `${getCharNumber(item.b_level)}审通过`
}
obj.v_user_name = item.v_dshusername
}
stepsData.value.push(obj)
})
stepsData.value.reverse()
console.log(stepsData.value)
})
.finally(() => {
loading.value = false
})
}
function computedConsumeTime(index: number) {
if (index === data.value.length - 1) {
return ''
function getCharNumber(number: number): string {
switch (number) {
case 1:
return '一'
case 2:
return '二'
case 3:
return '三'
case 4:
return '四'
case 5:
return '五'
case 6:
return '六'
case 7:
return '七'
case 8:
return '八'
default:
return ''
}
if (data.value.length === 1) {
return ''
}
return computedTimeDifference(data.value[index].b_sh_date, data.value[index + 1].b_sh_date)
}
function computedTimeDifference(date1: string, date2: string): string {
// 将日期字符串转换为 Date 对象
@@ -63,46 +153,165 @@ function computedTimeDifference(date1: string, date2: string): string {
// 如果没有任何值,说明两个日期相等,返回"0秒"
return result.length > 0 ? result.join('') : '0秒'
}
// 计算步骤的颜色
function getTimelineStyle(item: any) {
const colorMap: Record<number, string> = {
0: '#B0B0B0',
1: '#6A9EF1',
2: '#70C070',
3: '#F17A7A',
}
const color = colorMap[item.type] || '#B0B0B0'
return {
'--dot-color': color,
'--line-color': color,
}
}
function computedStepText(item): string {
switch (item.type) {
case 0:
return '未开始' // 未开始,灰色
case 1:
return '进行中'
// 进行中,蓝色
case 2:
return '已完成' // 已完成,绿色
case 3:
return '驳回' // 已驳回,红色
default:
return '未开始' // 默认灰色
}
}
function formatDateTime(dateTime: string): string {
if (!dateTime) {
return ''
}
return dateTime.replace(/\.\d+$/, '')
}
</script>
<template>
<LoadingPage v-if="loading" />
<view v-else-if="data.length > 0" class="timeline">
<view v-for="(item, index) in data" :key="index" class="timeline-item">
<view class="timeline-col left-col">
<view class="timeline-content left">
{{ item.b_name }}
</view>
<view v-else-if="stepsData.length > 0" class="">
<view style="display: flex;align-items: center;">
<text style="font-size: 28rpx; margin-right: 8rpx;">
当前进度 -
</text>
<view style="flex:1">
<sar-progress-bar
:percent="computedProgress"
inside
striped
animated
/>
</view>
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.v_user_name" class="title">
{{ item.v_user_name }}
<text v-if="item.v_tosh_user_name">
> {{ item.v_tosh_user_name }}
</text>
</view>
<view class="timeline">
<view
v-for="(item, index) in stepsData"
:key="index"
class="timeline-item"
:style="getTimelineStyle(item)"
>
<view class="timeline-col left-col">
<view class="timeline-content left">
<text>
{{ item.b_name }}
</text>
<view style="margin-top: 8rpx">
<sar-tag v-if="item.type === 1" theme="primary" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else-if="item.type === 2" theme="success" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else-if="item.type === 3" theme="danger" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else theme="secondary" size="small">
{{ computedStepText(item) }}
</sar-tag>
</view>
</view>
<view>{{ item.b_sh_date }}</view>
<view v-if="computedConsumeTime(index)">
审批耗时:{{ computedConsumeTime(index) }}
</view>
<view>{{ item.b_sh_memo }}</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.b_level === 0">
<view>
<text>操作人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>操作时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
</view>
<view v-else>
<view v-if="item.type === 0 || item.type === 1">
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
</view>
<view v-else-if="item.type === 3">
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>审核时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
<view>
<text>审核耗时:</text>
<text>{{ item.consume_time }}</text>
</view>
</view>
<view v-else>
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>审核时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
<view>
<text>审核意见:</text>
<text>{{ item.b_sh_memo }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
</view>
</view>
</view>
</view>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;"/>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;" />
</template>
<style lang="scss" scoped>
/* 整个容器 */
.timeline {
display: flex;
flex-direction: column;
}
/* 每个步骤项 */
.timeline-item {
position: relative;
@@ -110,6 +319,7 @@ function computedTimeDifference(date1: string, date2: string): string {
align-items: center;
padding: 32rpx 0;
}
/* 左右列容器,用来固定左右比例,不受内容内 padding 影响 */
.timeline-col {
box-sizing: border-box;
@@ -127,34 +337,30 @@ function computedTimeDifference(date1: string, date2: string): string {
padding-left: 32rpx; /* 给中间圆点留出空间 */
text-align: left;
}
/* 左右内容,可以自由添加 padding */
.timeline-content {
font-size: 28rpx;
word-break: break-all;
border-radius: 12rpx;
}
.timeline-content.left {
padding: 8rpx;
font-weight: bold;
text-align: center;
background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
border-radius: 12rpx; /* 让边角更柔和 */
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
.timeline-content.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
color: #666;
background: #f5f5f5;
.title {
&.left {
padding: 8rpx;
font-weight: bold;
color: black;
text-align: center;
// background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
// border-radius: 12rpx; /* 让边角更柔和 */
// box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
&.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
background: #f5f5f5;
}
}
/* 中间圆点容器,绝对定位在左右列分界处 */
.timeline-center {
position: absolute;
@@ -169,8 +375,9 @@ function computedTimeDifference(date1: string, date2: string): string {
.timeline-dot {
width: 24rpx;
height: 24rpx;
background: #007aff;
background: var(--dot-color, #007aff);
border-radius: 50%;
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.5);
}
/* 使用每个步骤项的伪元素绘制竖线 */
.timeline-item::before {
@@ -180,7 +387,7 @@ function computedTimeDifference(date1: string, date2: string): string {
left: calc(30%);
width: 4rpx; /* 可随意调整宽度 */
content: '';
background: #409eff; /* 竖线颜色 */
background: #dddddd; /* 竖线颜色 */
transform: translateX(-50%);
}
/* 第一项:只显示圆点下方的线 */
@@ -0,0 +1,201 @@
<script lang="ts" setup>
import LoadingPage from '@/components/loading-page/index.vue'
import { loaddataApi } from '@/request/api'
const props = defineProps({
componentData: {
type: Object,
},
})
const data = ref<any[]>([])
const loading = ref<boolean>(true)
onLoad(() => {
initData()
})
async function initData() {
loading.value = true
await loaddataApi({
viewname: 'v_sh_record',
orderfield: 'b_id desc',
searchcondition: `b_module_id = '${props.componentData.b_module_id}' and b_dj_id = '${props.componentData.b_id}'`,
soption: '',
}).then((res) => {
data.value = res.data
})
loading.value = false
}
function computedConsumeTime(index: number) {
if (index === data.value.length - 1) {
return ''
}
if (data.value.length === 1) {
return ''
}
return computedTimeDifference(data.value[index].b_sh_date, data.value[index + 1].b_sh_date)
}
function computedTimeDifference(date1: string, date2: string): string {
// 将日期字符串转换为 Date 对象
const d1 = new Date(date1)
const d2 = new Date(date2)
// 计算两个日期之间的毫秒差值
const diffInMilliseconds = Math.abs(d2.getTime() - d1.getTime())
// 将毫秒差值转换为对应的天、小时、分钟和秒
const days = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24))
const hours = Math.floor((diffInMilliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((diffInMilliseconds % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diffInMilliseconds % (1000 * 60)) / 1000)
// 根据条件组合输出结果
const result: string[] = []
if (days > 0)
result.push(`${days}天`)
if (hours > 0)
result.push(`${hours}时`)
if (minutes > 0)
result.push(`${minutes}分`)
if (seconds > 0)
result.push(`${seconds}秒`)
// 如果没有任何值,说明两个日期相等,返回"0秒"
return result.length > 0 ? result.join('') : '0秒'
}
</script>
<template>
<LoadingPage v-if="loading" />
<view v-else-if="data.length > 0" class="timeline">
<view v-for="(item, index) in data" :key="index" class="timeline-item">
<view class="timeline-col left-col">
<view class="timeline-content left">
{{ item.b_name }}
</view>
</view>
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.v_user_name" class="title">
{{ item.v_user_name }}
<text v-if="item.v_tosh_user_name">
> {{ item.v_tosh_user_name }}
</text>
</view>
<view>{{ item.b_sh_date }}</view>
<view v-if="computedConsumeTime(index)">
审批耗时:{{ computedConsumeTime(index) }}
</view>
<view>{{ item.b_sh_memo }}</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
</view>
</view>
</view>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;" />
</template>
<style lang="scss" scoped>
/* 整个容器 */
.timeline {
display: flex;
flex-direction: column;
}
/* 每个步骤项 */
.timeline-item {
position: relative;
display: flex;
align-items: center;
padding: 32rpx 0;
}
.timeline-item:first-child {
padding-top: 0;
}
/* 左右列容器,用来固定左右比例,不受内容内 padding 影响 */
.timeline-col {
box-sizing: border-box;
}
.left-col {
width: 30%;
padding-right: 32rpx; /* 给中间圆点留出空间 */
font-size: 28rpx;
text-align: right;
}
.right-col {
width: 70%;
padding-left: 32rpx; /* 给中间圆点留出空间 */
text-align: left;
}
/* 左右内容,可以自由添加 padding */
.timeline-content {
font-size: 28rpx;
word-break: break-all;
border-radius: 12rpx;
}
.timeline-content.left {
padding: 8rpx;
font-weight: bold;
text-align: center;
background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
border-radius: 12rpx; /* 让边角更柔和 */
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
.timeline-content.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
color: #666;
background: #f5f5f5;
.title {
font-weight: bold;
color: black;
}
}
/* 中间圆点容器,绝对定位在左右列分界处 */
.timeline-center {
position: absolute;
left: calc(30%);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
transform: translateX(-50%);
}
/* 圆点 */
.timeline-dot {
width: 24rpx;
height: 24rpx;
background: #007aff;
border-radius: 50%;
}
/* 使用每个步骤项的伪元素绘制竖线 */
.timeline-item::before {
position: absolute;
top: 0;
bottom: 0;
left: calc(30%);
width: 4rpx; /* 可随意调整宽度 */
content: '';
background: #409eff; /* 竖线颜色 */
transform: translateX(-50%);
}
/* 第一项:只显示圆点下方的线 */
.timeline-item:first-child::before {
top: 50%;
}
/* 最后一项:只显示圆点上方的线 */
.timeline-item:last-child::before {
bottom: 50%;
}
</style>
@@ -36,7 +36,7 @@ async function onSubmit() {
const searchcondition = `b_id = '${userStore.userInfo.userid}' AND ISNULL(b_password, '') = ISNULL('${formModel.oldPassword}', '')`
const searchRes = await loaddataApi({
viewname: 'v_mini_loginid',
viewname: 'b_user',
searchcondition: `${searchcondition}`,
orderfield: 'b_id',
soption: '',
@@ -58,7 +58,7 @@ async function onSubmit() {
const delValues = []
const keys = []
tables.push('b_truck')
tables.push('b_user')
insertValues.push('[]')
updateValues.push(JSON.stringify([updateObj]))
delValues.push('[]')
+72 -42
View File
@@ -26,50 +26,80 @@ export const useCountsStore = defineStore('taskCounts', () => {
// 请求的 SQL 查询列表
const sqlQueries = {
fukuan: `
SELECT COUNT(*) AS all_cnt,
SUM(CASE WHEN rs.b_level >= st.b_level THEN 1 ELSE 0 END) AS v_ash,
SUM(CASE WHEN rs.b_level = st.b_level - 1 THEN 1 ELSE 0 END) AS v_dsh
FROM (
SELECT r.*
FROM (
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) AS a
JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) AS rs
LEFT JOIN (
SELECT b_module_id, b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
) AS st
ON rs.b_module_id = st.b_module_id
AND (rs.b_level = st.b_level - 1 OR rs.b_level = st.b_level)
WHERE rs.b_module_id = 'cw_zdorder_f';
SELECT
COUNT(1) AS all_cnt,
SUM(CASE
WHEN rs.b_level >= st.b_level THEN 1
ELSE 0
END) AS v_ash,
SUM(CASE
WHEN rs.b_level = st.b_level - 1 THEN 1
ELSE 0
END) AS v_dsh
FROM
(
-- 只取每个单据的最新一条
SELECT r.*
FROM
(
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) a
INNER JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) rs
CROSS JOIN
(
SELECT TOP 1 b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
AND b_module_id = 'cw_zdorder_f'
ORDER BY b_level DESC
) st
WHERE rs.b_module_id = 'cw_zdorder_f';
`,
baoxiao: `
SELECT COUNT(*) AS all_cnt,
SUM(CASE WHEN rs.b_level >= st.b_level THEN 1 ELSE 0 END) AS v_ash,
SUM(CASE WHEN rs.b_level = st.b_level - 1 THEN 1 ELSE 0 END) AS v_dsh
FROM (
SELECT r.*
FROM (
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) AS a
JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) AS rs
LEFT JOIN (
SELECT b_module_id, b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
) AS st
ON rs.b_module_id = st.b_module_id
AND (rs.b_level = st.b_level - 1 OR rs.b_level = st.b_level)
WHERE rs.b_module_id = 'car_daily';
SELECT
COUNT(1) AS all_cnt,
SUM(CASE
WHEN rs.b_level >= st.b_level THEN 1
ELSE 0
END) AS v_ash,
SUM(CASE
WHEN rs.b_level = st.b_level - 1 THEN 1
ELSE 0
END) AS v_dsh
FROM
(
SELECT r.*
FROM
(
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) a
INNER JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
AND r.b_module_id = 'car_daily'
) rs
CROSS JOIN
(
SELECT TOP 1 b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
AND b_module_id = 'car_daily'
ORDER BY b_level DESC
) st
`,
}
+8 -24
View File
@@ -5,30 +5,14 @@ import { getModuleQxApi } from '@/request/api'
import { useUserStore } from './user'
const PERMISSION_PATH_MAP = [
// {
// moduleId: 'xc_consite_checkcon',
// path: '/pages-sub/jiaoyan',
// },
// {
// moduleId: 'xc_consite_clearcon',
// path: '/pages-sub/qingxi',
// },
// {
// moduleId: 'xc_consite_jiarecon',
// path: '/pages-sub/jiare',
// },
// {
// moduleId: 'xc_consite_wxcon',
// path: '/pages-sub/weixiu',
// },
// {
// moduleId: 'xc_conyard_in',
// path: '/pages-sub/jinchang',
// },
// {
// moduleId: 'xc_conyard_out',
// path: '/pages-sub/chuchang',
// },
{
moduleId: 'cw_zdorder_f',
path: '/pages-sub/fukuan',
},
{
moduleId: 'car_daily',
path: '/pages-sub/baoxiao',
}
]
export const usePermissionStore = defineStore(